Squashed 'third_party/SPIRV-Tools/' changes from d14db341b..9559cdbdf

9559cdbdf Fix #2609 - Handle out-of-bounds scalar replacements. (#2767)
f54b8653d Limit fuzzer tests so that they take less time to run (#2763)
bb0e2f65b Fix check for unreachable blocks in merge-return (#2762)
1a89ac8b2 Transformation and fuzzer pass to add dead continues (#2758)
65f49dfc3 Remove unneeded future imports (#2739)
c7fcb8c3b Process OpDecorateId in ADCE (#2761)
fb83b6fbb Record correct dominators in merge return (#2760)
c9190a54d SSA rewriter: Don't use trivial phis (#2757)
aea4e6b1b Fix block depth rule priority (#2755)
a94ddc267 Case validation with repeated labels (#2689)
3855447d9 Bindless Instrument: Make init check depend solely on input_init_enabled (#2753)
11516c0b9 Validate storage class OpenCL environment rules for atomics (#2750)
bac82f49a Allow LOD ops in compute shaders with derivative group execution modes (#2752)
76b75c40a Document opt::Instruction::InsertBefore methods (#2751)
aa9e8f538 Revert "Do not inline OpKill Instructions (#2713)" (#2749)
58e2ec25b For Vulkan, disallow structures containing opaque types (#2546)
230c9e437 Fix bug in merge return (#2734)
1fedf72e5 Allow ray tracing shaders in inst bindle check pass. (#2733)
032adc4d7 Correctly implement WebGPU related flag exclusions (#2737)
92c41ff1e Remove Common Uniform Elimination Pass (#2731)
59de04ad6 BUILD.gn: Add deps and move files for `gn check` (#2735)
55adf4cf7 Update execution scope rules for WebGPU (#2730)
1a2de48a1 Extra small storage validation (#2732)
327963765 Add validation for SPV_EXT_demote_to_helper_invocation (#2707)
508151250 BUILD.gn: Add targets to build all command-line tools (#2727)
5ce8cf781 Change the order branches are simplified in dead branch elim (#2728)
cd153db8e Add —preserve-bindings and —preserve-spec-constants (#2693)
86e45efe1 Handle decorations better in some optimizations (#2716)
3a252a267 Update memory scope rules for WebGPU (#2725)
0c4feb643 Remove extra semis (#2717)
456cc598a Validate usage of 8- and 16-bit types with only storage capabilities (#2704)
b8ab80843 Shrinker for spirv-fuzz (#2708)
37e8f7994 Perform merge return with single return in loop. (#2714)
fe7cc9c61 Do not inline OpKill Instructions (#2713)
5a93e0739 Refactor reducer options (#2709)
a6bfc26e5 Fix BUILD.gn for Fuchsia platform build. (#2692)
9702d47c6 Validate that in OpenGL env block variables have Binding (#2685)
e6e3e2ccc Update type for loaded builtin GlobalInvocationID in pass instrumentation (#2705)
6ccb52b86 Warn when input facts are invalid. (#2699)
88183041d Got rid of redundant declaration. (#2698)
efde68236 Disallow movement of unreachable blocks. (#2700)
dfcb5a1e1 Refactor fuzzer transformations (#2694)
888aeef8a Fix Component decoration validation for arrays (#2697)
df86bb44f Replace global static map with an array of pairs (#2691)
7c294608c Basic validation for Component decorations (#2679)
69b945992 Add infrastructure for maintaining and using DEPS (#2684)
2b84d25f1 Fix store to uniform Vulkan check (#2688)
bec7e0393 Add all accepted target environments to the tools' help texts (#2687)
51b0d5ce5 Represent uniform facts via descriptor set and binding. (#2681)
fa981bc24 Roll external/spirv-headers/ 8b911bd2b..de99d4d83 (8 commits) (#2682)
a132c9b64 Whitelist SPV_GOOGLE_user_type. (#2673)
001e823b6 Add fuzzer pass to obfuscate constants. (#2671)
2090d7a2d Handle volatile memory semantics in upgrade (#2674)
3d5fb7b90 Validate Volatile memory semantics bit (#2672)
400dbde0b Disallow stores to UBOs (#2651)
6cc2c8f4a Another fix uint -> uint32_t (#2676)
59983a601 Validate variable initializer type (#2668)
9477c91de Fix uint -> uint32_t in fuzz.cpp (#2675)
42830e5a6 Add replayer tool for spirv-fuzz. (#2664)
b4bf7bcf0 Add validation for Subgroup builtins (#2637)
9c0830133 Add constant == uniform facts. (#2660)
208d3132e Cast __LINE__ to size_t (#2661)
a8ae579f7 Add transformation to replace a boolean constant with a numeric comparison (#2659)
0755d6ce8 Add builtin validation for SPV_NV_shader_sm_builtins (#2656)
43fb2403a Instrument: Fix code for version 2 output format. (#2655)
08cc49ec5 Fix bug in 'split blocks', and add tests for fuzzer. (#2658)
d01a3c3b4 Optimizer: Handle array type with OpSpecConstantOp length (#2652)
4a00a80c4 Add fuzzer pass to add dead breaks. (#2654)
620197bd6 Add fuzzer pass that adds useful constructs to a module (#2647)
2c0111e6e Add validation for SPV_EXT_fragment_shader_interlock (#2650)
699e167d7 Remove asserts from GetUnderlyingType (#2646)
7919b877c Close opened file handles. (#2644)
f99d7ad5c Validate OpenCL rules for ImageRead and OpImageSampleExplicitLod (#2643)
209ff0ce9 Add spirv-fuzz pass to permute blocks. (#2642)
e7866de4b Linker: Better type comparison for OpTypeArray and OpTypeForwardPointer (#2580)
0125b28ed Add compact ids to WebGPU <-> Vulkan transformations (#2639)
3d62cb814 Instrument: Add version 2 of record formats (#2630)
1b71e4533 Add "split block" transformation. (#2633)
f05181234 Add WebGPU specific fuzzer for validation (#2628)
5a06fa466 Add fuzzer for Vulkan->WebGPU spirv-opt passes (#2626)
78b2b1866  Add fuzzer for WebGPU->Vulkan spirv-opt passes (#2625)
6c7db9c63 Handle nested breaks from switches. (#2624)
37ae8671a Add spirv-fuzz tool. (#2631)
fe9f87013 Add library for spirv-fuzz (#2618)
42abaa099 Remove MarkV and Stats code. (#2576)
3b5ab540c linker: Add tests for various type comparisons (#2613)
4c73ebc46 Enable the base branch to be overridden in the code format check script (#2607)
b8fe7211c Allow arrays of out per-primitive builtins for mesh shaders (#2617)
07a101971 Validate OpenCL environment rules for OpImageWrite (#2619)
b0504239a Added an external dependency on protobufs, included when SPIRV_BUILD_FUZZER is defined, so that they can be used by the (upcoming) spirv-fuzz tool.  Also updated the kokoro build scripts, for relevant targets, to clone an appropriate tag of the protobufs repo, and to pass -DSPIRV_BUILD_FUZZER to the configurations for which we intend to ultimately build spirv-fuzz. (#2616)
4557d0858 Add in individual flags for Vulkan <-> WebGPU passes (#2615)
13f61bf85 Update vloadn and vstoren validation to match the OpenCL Extended Instruction Set Specification (#2599)
d9c00e1d2 Add folding rules for OpQuantizeToF16 (#2614)
713da30b6 Disallow merge targeting block with OpLoopMerge (#2610)
60aaafbc7 Allows breaks selection breaks to switches (#2605)
0982f0212 Using the instruction folder to fold OpSpecConstantOp (#2598)
9f035269d Validate OpenCL environment rules for OpTypeImage (#2606)
47741f050 Validate OpenCL memory and addressing model environment rules (#2589)
ff4feb44b Validate construct exits (#2459)
9dfd4b835 Bindless Validation: Instrument descriptor-based loads and stores (#2583)
7e7745fce Validate loop merge (#2579)
fc7b5d8c6 Mem model spv 1.4 (#2565)
84aa4946e Start SPIRV-Tools v2019.4
ce19e217b Finalize SPIRV-Tools v2019.3
84503583c Handle id overflow in sroa better. (#2582)
f815e6fe5 Update CHANGES
e935dac9e Make pointers to isomorphic type interchangeable with option. (#2570)
2947e88f7 Update instrumentation passes to handle 1.4 interfaces (#2573)
06ce59b0b Instrument: Fix load type of pre-existing builtin (#2575)
87c4ef8a9 Do not fold floating point if float controls used (#2569)
45fb69666 Use last version (#2578)
f6d9a1784 Add pass to fix some invalid unreachable blocks for WebGPU (#2563)
89fe836fe Fix clang-tidy warning about definition/declaration mismatch. (#2571)
f2803c4a7 VK_KHR_uniform_buffer_standard_layout validation (#2562)
cc3e93c4e Add tests for folding 1.4 selects (#2568)
ea5e1b62e Update priv-to-local for SPIR-V 1.4 (#2567)
d0a1f5a05 spvtest::Validate::CompileFailure: Don't leak the diagnostic (#2564)
b74d92a8c ADCE support for SPIR-V 1.4 entry points (#2561)
2b65a71d4 Fix use of 'is' operator for comparison (#2547)
63f57d95d Support SPIR-V 1.4 (#2550)
106c98d0f Validate sign of int types. (#2549)
eef11cdb7 Update README with links to build artifacts. (#2548)
5fc5303ee [spirv] Package and upload builds. (#2544)
6d04da22c Fix up type mismatches. (#2545)
c8b09744c Add validation specific to OpExecutionModeId (#2536)
a5da68d44 Remove stale comment (#2542)
32af42616 Change implementation of post order CFG traversal (#2543)
64faf6d9c Fix undefined bit shift in sroa. (#2532)
dca3ea5e1 fix example.cpp (#2540)
2de3e3c02 Add spirv-lesspipe.sh into SPIRV_SKIP_EXECUTABLES umbrella (https://github.com/KhronosGroup/SPIRV-Tools/issues/2497) (#2504)
fb08224f8 Fix spirv-headers link in the README. (#2516)
b68af7ca8 Add support for Private & Output to initializer decompose flag (#2537)
736376dbf Remove Acquire, Release, and Relaxed from allowed Mem Sem bits for WebGPU (#2526)
07c4dd4b9 Reduce runtime of array layout checks (#2534)
7aad9653f Remove legacy utility functions (#2530)
d754b7059 Shorten names of cmake targets (#2531)
ac878fcbd Remove unreachable block validation (#2525)
21712068f Validate that SPIR-V binary is encoded as little endian for WebGPU (#2523)
3aad3e922 Change validation of memory semantics for OpAtomics* in WebGPU (#2519)
048dcd38c Implement WebGPU->Vulkan initializer conversion for 'Function' variables (#2513)
3335c6114 reduce: Add two branch reduction passes (#2507)
102e430a8 Add pass to legalize OpVectorShuffle for WebGPU (#2509)
07ac7dee5 SPIRV-Tools requires python3 (#2510)
98b3f26c2 Gate formatless checks on Vulkan env (#2486)
2b46521cd Add -fsantize to link flags. (#2506)
82ebbbba1 README: fix formatting of requiring Python 'future' (#2500)
9047de51c Accept OpBitCast in fix storage class. (#2505)
d90aae9a5 reduce: miscellaneous fixes (#2494)
7ce37d66a Fix use of Logf to avoid format security warning (#2498)
0cb2d4079 Add WebGPU->Vulkan and Vulkan->WebGPU flags in spirv-opt (#2496)
9766b22b3 spirv-opt: Behave a bit better in the face of unknown instructions (#2487)
3a0bc9e72 Add fix storage class code. (#2434)
e8c2d95ed Fix webgpu header file name in BUILD.gn (#2493)
26c1b8878 Update CHANGES
236bdc006 Change prioritization of unreachable merge and continue (#2460)
12e4a7b64 Handle variable pointer in some optimizations (#2490)
01964e325 Add pass to generate needed initializers for WebGPU (#2481)
4bd106b08 Handle dead infinite loops in DCE (#2471)
8129cf2f9 Remove merge assert in block calculation (#2489)
e2ddb9371 reduce: add remove_selection_reduction_opportunity (#2485)
c9874e509 Fix merge return in the face of breaks (#2466)
0300a464a Maintain inst to block mapping in merge return (#2469)
320a7de5c Validate that OpUnreacahble is not statically reachable (#2473)
fcb845310 reduce: fix loop to selection pass for loops with combined header/continue block (#2480)
2ff54e34e Handle function decls in Structured CFG analysis (#2474)
42e6f1aa6 Add option to validate after each pass (#2462)
fb0753640 reduce: fix loop to selection dominance query (#2477)
7d1b176c1 Improve reducer algorithm and other changes (#2472)
ffbecae56 Check OpSampledImage is only passed into valid instructions (#2467)
2d52cbee4 Add some val options to reduce (#2401)
1f60f9896 reduce: remove unreferenced blocks pass (#2398)
08b54d9e4 Convert sampled consumers to being Instructions instead of IDs (#2464)
e1a76269b Bindless Validation: Descriptor Initialization Check (#2419)
9244e6ff6 Reverting commit da5a780ff9fff7e226ca84728075adabc4d1608c
da5a780ff Variable pointers cannot be an operand to OpArrayLength
2ac348b5c Repair test for unused storage buffer with descriptor (#2436)
e54552214 Add --strip-atomic-counter-memory (#2413)
bdcb15516 Relax function call parameter check (#2448)
5186ffedb Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
6df8a917a Add validation of storage classes for WebGPU (#2446)
a5c06c903 Validator: no Storage comparison for pointer param (#2428)
9d29c37ac Removing decorations when doing constant propagation. (#2444)
b75f4362f Add validation for ExecutionMode in WebGPU (#2443)
b1ff15f22 Add missing DepthGreater case to Fragment only check (#2440)
b12e7338e Implement WebGPU specific CFG validation (#2386)
a2ef7be24 Add Linux ASAN bot configs. (#2427)
07f80c4df Fix python scripts to work with python3 (#2426)
86f6ac086 Add a test for two back-edges in a continue (#2418)
5fb83a970 Allow NonWritable to target struct members. (#2420)
32b0f6739 Use correct option in spvTextToBinary. (#2416)
d800bbbac Handle back edges better in dead branch elim. (#2417)
002ef361c Add validation for SPV_NV_cooperative_matrix (#2404)
fc3897b5f Validate: (data) Block can't appear within a Block (#2410)
37b584a73 Fixed undefined reference to 'clock_gettime' by linking rt library (#2409)
a006cbc1d Non memory object as parameters. (#2415)
4c43afcad It is invalid to apply both Restrict and Aliased to the same <id> (#2408)
fde69dcd8 Fix OpDot folding of half float vectors. (#2411)
8eddde2e7 Don't change type of input and output var in dead member elim (#2412)
76730a46a In Vulkan, disallow BufferBlock on StorageBuffer variables  (#2380)
5994ae2a0 Start SPIRV-Tools 2019.3
61dfd8492 Finalize SPIRV-Tools 2019.2
bf23ed887  OpAtomicLoad, OpAtomicStore, OpAtomicExchange can operate on int or float value. Except for Vulkan environment that only operates on int value. (#2385)
80496f42a Update CHANGES
2f84b5de9 Bindless: Fix computation of set and binding for runtime bounds check (#2384)
528fea2b1 Fixup unused variables (#2402)
4b42cd19d BUILD.gn: Only build tests in Chromium. (#2392)
6d20f6257 Refactor webgpu-mode pass ran tests to be parameterized (#2395)
78ac954c4 Mark type id of unknown instructions at fully used. (#2399)
9540f2d98 Instrumentation: Fix instruction index when multiple functions (#2389)
1b0047f21 Add pass to remove dead members. (#2379)
0167a20b0 Move usage detection to after all instructions are registered (#2378)
0187c190b Fix BUILD.gn (#2381)
354205b3d Don't merge unreachable blocks (#2375)
40a7940e0 Fix merge blocks opportunity to check if still enabled (#2370)
12b3d7e9d Add strip-debug to webgpu-mode passes (#2368)
a0ff7c512 Add better error message to ValidateInstructions (#2365)
34c5ac614 Fixes #2358.  Added to the reducer the ability to remove a function t… (#2361)
10a7def6c Smoketest should use KhronosGroup glslang (#2363)
39bfb6b97 Make spvParseTargetEnv public (#2362)
cf2114613 Expand bindless bounds checking to runtime-sized descriptor arrays (#2316)
9b6ba4d1c Allow arrayed storage images for NonWritable decoration (#2358)
117a1fd11 Validate variable pointer related function call rules (#2270)
0f4bf0720 Add flatten-decorations flag to webgpu-mode flags (#2348)
368567bc5 Convert deprecated GoogleTest fixture to the new version (#2357)
adbbe2024 Mention the reducer tool and location in the README. (#2341)
e3c64a374 Do not build spirv-reduce on iOS/tvOS/watchOS - it requires std::system which is not available on those platforms (#2355)
37861ac10 Merge blocks in reducer (#2353)
453b7c85c Fix up some test (#2351)
846d12afe Add whitelist for decorations in WebGPU (#2346)
63e032f91 Remove unused lambda capture (#2350)
3b6fee3da Fixes #2338.  Added functionality to remove OpPhi instructions (replacing their uses) when merging blocks (#2339)
2acbf488b Add WebGPU specific validation for WorkgroupSize BuiltIn decoration (#2334)
e2f462262 Add WebGPU specific validation for multiple BuiltIn decorations (#2333)
3d2afb78c Add whitelist of allowed BuiltIn decorations for WebGPU (#2337)
d17fcf8ab Add WebGPU validation for LocalInvocationIndex BuiltIn decoration (#2335)
837153ccd Add WebGPU specific validation for FragDepth BuiltIn decoration (#2332)
0c14583f1 Add WebGPU specific validation for FragCoord BuiltIn decoration (#2331)
b6698e0d8 Add WebGPU specific validation for FrontFacing BuiltIn decoration (#2330)
734def144 Add WebGPU specific validation for InstanceIndex BuiltIn decoration (#2329)
b947ecfe7 Add WebGPU specific validation for VertexIndex BuiltIn decoration (#2328)
464111eae Remove use of deprecated googletest macro (#2286)
7f1a020ab Fix test_val_limits MSVC build (#2347)
5d6b4c4b1 Move CodeGenerator to be accessible by other validation tests (#2343)
cf7834623 Add source/opt/block_merge_util.* to BUILD.gn (#2344)
7f3679a8b Validate NonWritable decoration (#2263)
9ab1c0ddd Remove code sinking for -O. (#2340)
98c67d385 Fixed names in ifdefs and GetName functions that had been forgotten in a previous refactoring.  Also shortened names of test files as those files test both the new 'finder' classes introduced in the refactoring, as well as the 'reduction pass' class; the shorter names capture both. (#2336)
3345fe6a9 Extracted block merging functionality into its own utility file (#2325)
cf011f990 More layout check fixes (#2315)
e2279da71 Remove the static maps from CheckDecorationsCompatibility (#2327)
8915a7c8f spirv-val: Emit an error when an OpSwitch target is not an OpLabel (#2298)
1e3c589a6 Add WebGPU specific validation for Position BuiltIn decoration (#2309)
20b2e2b9f Add SpirvTools::IsValid(). (#2326)
86d0d9be2 Refactored reducer so that the 'finding' functionality of a reduction pass are separated from the generic functionality for tracking progress of a pass.  With this change, we now have a ReductionOpportunityFinder abstract class, with many subclasses for each type of reduction, and just one ReductionPass class, which has an associated finder. (#2321)
b1be6763f Add helper for 'is Vulkan or WebGPU' (#2324)
3d7102424 Added some documentation about the reducer. (#2318)
4a405eda5 Fix layout checks for nested struct in relaxed layout; and descriptor arrays (#2312)
3a3ad2ec5 Add utility to generate a logging string for a given environment (#2314)
a64c651e1 Fix Constants Analyses bug inserted by #2302 (#2306)
eab06d669 Check forward reference in OpTypeArray. (#2307)
8df947d2d Handle instructions not in blocks in code sinking. (#2308)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: 9559cdbdf011c487f67f89e2d694bd4a18d5c1e0
diff --git a/.appveyor.yml b/.appveyor.yml
index a50c7c2..77cd89c 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -42,7 +42,7 @@
   - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip"
   - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip
   - 7z x ninja.zip -oC:\ninja > nul
-  - set PATH=C:\ninja;%PATH%
+  - set PATH=C:\ninja;C:\Python36;%PATH%
 
 before_build:
   - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
@@ -59,11 +59,11 @@
 
 build_script:
   - mkdir build && cd build
-  - cmake -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF ..
+  - cmake -GNinja -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF ..
   - ninja install
 
 test_script:
-  - ctest -C %CONFIGURATION% --output-on-failure --timeout 300
+  - ctest -C %CONFIGURATION% --output-on-failure --timeout 310
 
 after_test:
   # Zip build artifacts for uploading and deploying
diff --git a/.gitignore b/.gitignore
index 059b18e..a965a99 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
 /external/spirv-headers
 /external/effcee
 /external/re2
+/external/protobuf
 /out
 /TAGS
 /third_party/llvm-build/
@@ -21,5 +22,5 @@
 *~
 
 # C-Lion
-.idea
-cmake-build-debug
\ No newline at end of file
+/.idea/
+/cmake-build-*/
diff --git a/Android.mk b/Android.mk
index 179cf26..82d9776 100644
--- a/Android.mk
+++ b/Android.mk
@@ -13,7 +13,6 @@
 		source/ext_inst.cpp \
 		source/enum_string_mapping.cpp \
 		source/extensions.cpp \
-		source/id_descriptor.cpp \
 		source/libspirv.cpp \
 		source/name_mapper.cpp \
 		source/opcode.cpp \
@@ -63,6 +62,7 @@
 		source/val/validate_instruction.cpp \
 		source/val/validate_memory.cpp \
 		source/val/validate_memory_semantics.cpp \
+		source/val/validate_misc.cpp \
 		source/val/validate_mode_setting.cpp \
 		source/val/validate_layout.cpp \
 		source/val/validate_literals.cpp \
@@ -70,19 +70,20 @@
 		source/val/validate_non_uniform.cpp \
 		source/val/validate_primitives.cpp \
 		source/val/validate_scopes.cpp \
+		source/val/validate_small_type_uses.cpp \
 		source/val/validate_type.cpp
 
 SPVTOOLS_OPT_SRC_FILES := \
 		source/opt/aggressive_dead_code_elim_pass.cpp \
 		source/opt/basic_block.cpp \
 		source/opt/block_merge_pass.cpp \
+		source/opt/block_merge_util.cpp \
 		source/opt/build_module.cpp \
 		source/opt/cfg.cpp \
 		source/opt/cfg_cleanup_pass.cpp \
 		source/opt/ccp_pass.cpp \
 		source/opt/code_sink.cpp \
 		source/opt/combine_access_chains.cpp \
-		source/opt/common_uniform_elim_pass.cpp \
 		source/opt/compact_ids_pass.cpp \
 		source/opt/composite.cpp \
 		source/opt/const_folding_rules.cpp \
@@ -91,19 +92,24 @@
 		source/opt/dead_branch_elim_pass.cpp \
 		source/opt/dead_insert_elim_pass.cpp \
 		source/opt/dead_variable_elimination.cpp \
+		source/opt/decompose_initialized_variables_pass.cpp \
 		source/opt/decoration_manager.cpp \
 		source/opt/def_use_manager.cpp \
 		source/opt/dominator_analysis.cpp \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
 		source/opt/eliminate_dead_functions_pass.cpp \
+		source/opt/eliminate_dead_functions_util.cpp \
+		source/opt/eliminate_dead_members_pass.cpp \
 		source/opt/feature_manager.cpp \
+		source/opt/fix_storage_class.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \
 		source/opt/folding_rules.cpp \
 		source/opt/fold_spec_constant_op_and_composite_pass.cpp \
 		source/opt/freeze_spec_constant_value_pass.cpp \
 		source/opt/function.cpp \
+		source/opt/generate_webgpu_initializers_pass.cpp \
 		source/opt/if_conversion.cpp \
 		source/opt/inline_pass.cpp \
 		source/opt/inline_exhaustive_pass.cpp \
@@ -114,6 +120,7 @@
 		source/opt/instrument_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
+                source/opt/legalize_vector_shuffle_pass.cpp \
 		source/opt/licm_pass.cpp \
 		source/opt/local_access_chain_convert_pass.cpp \
 		source/opt/local_redundancy_elimination.cpp \
@@ -149,8 +156,10 @@
 		source/opt/scalar_replacement_pass.cpp \
 		source/opt/set_spec_constant_default_value_pass.cpp \
 		source/opt/simplification_pass.cpp \
+		source/opt/split_invalid_unreachable_pass.cpp \
 		source/opt/ssa_rewrite_pass.cpp \
 		source/opt/strength_reduction_pass.cpp \
+		source/opt/strip_atomic_counter_memory_pass.cpp \
 		source/opt/strip_debug_info_pass.cpp \
 		source/opt/strip_reflect_info_pass.cpp \
 		source/opt/struct_cfg_analysis.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 69e3dc8..70772b9 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -12,10 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//build_overrides/spirv_tools.gni")
-
-import("//testing/test.gni")
 import("//build_overrides/build.gni")
+import("//build_overrides/spirv_tools.gni")
+if (build_with_chromium) {
+  import("//testing/test.gni")
+}
 
 spirv_headers = spirv_tools_spirv_headers_dir
 
@@ -293,11 +294,11 @@
 
 source_set("spvtools_headers") {
   sources = [
+    "include/spirv-tools/instrument.hpp",
     "include/spirv-tools/libspirv.h",
     "include/spirv-tools/libspirv.hpp",
     "include/spirv-tools/linker.hpp",
     "include/spirv-tools/optimizer.hpp",
-    "include/spirv-tools/instrument.hpp",
   ]
 
   public_configs = [ ":spvtools_public_config" ]
@@ -305,7 +306,6 @@
 
 static_library("spvtools") {
   deps = [
-    ":spvtools_core_enums_unified1",
     ":spvtools_core_tables_unified1",
     ":spvtools_generators_inc",
     ":spvtools_glsl_tables_glsl1-0",
@@ -376,14 +376,15 @@
   ]
 
   public_deps = [
+    ":spvtools_core_enums_unified1",
     ":spvtools_headers",
   ]
 
-  configs -= [ "//build/config/compiler:chromium_code" ]
-  configs += [
-    "//build/config/compiler:no_chromium_code",
-    ":spvtools_internal_config",
-  ]
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+  configs += [ ":spvtools_internal_config" ]
 }
 
 static_library("spvtools_val") {
@@ -422,10 +423,12 @@
     "source/val/validate_logicals.cpp",
     "source/val/validate_memory.cpp",
     "source/val/validate_memory_semantics.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_scopes.cpp",
+    "source/val/validate_small_type_uses.cpp",
     "source/val/validate_type.cpp",
     "source/val/validation_state.cpp",
   ]
@@ -437,11 +440,11 @@
     ":spvtools_headers",
   ]
 
-  configs -= [ "//build/config/compiler:chromium_code" ]
-  configs += [
-    "//build/config/compiler:no_chromium_code",
-    ":spvtools_internal_config",
-  ]
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+  configs += [ ":spvtools_internal_config" ]
 }
 
 static_library("spvtools_opt") {
@@ -452,6 +455,8 @@
     "source/opt/basic_block.h",
     "source/opt/block_merge_pass.cpp",
     "source/opt/block_merge_pass.h",
+    "source/opt/block_merge_util.cpp",
+    "source/opt/block_merge_util.h",
     "source/opt/build_module.cpp",
     "source/opt/build_module.h",
     "source/opt/ccp_pass.cpp",
@@ -464,8 +469,6 @@
     "source/opt/code_sink.h",
     "source/opt/combine_access_chains.cpp",
     "source/opt/combine_access_chains.h",
-    "source/opt/common_uniform_elim_pass.cpp",
-    "source/opt/common_uniform_elim_pass.h",
     "source/opt/compact_ids_pass.cpp",
     "source/opt/compact_ids_pass.h",
     "source/opt/composite.cpp",
@@ -482,6 +485,8 @@
     "source/opt/dead_insert_elim_pass.h",
     "source/opt/dead_variable_elimination.cpp",
     "source/opt/dead_variable_elimination.h",
+    "source/opt/decompose_initialized_variables_pass.cpp",
+    "source/opt/decompose_initialized_variables_pass.h",
     "source/opt/decoration_manager.cpp",
     "source/opt/decoration_manager.h",
     "source/opt/def_use_manager.cpp",
@@ -494,8 +499,14 @@
     "source/opt/eliminate_dead_constant_pass.h",
     "source/opt/eliminate_dead_functions_pass.cpp",
     "source/opt/eliminate_dead_functions_pass.h",
+    "source/opt/eliminate_dead_functions_util.cpp",
+    "source/opt/eliminate_dead_functions_util.h",
+    "source/opt/eliminate_dead_members_pass.cpp",
+    "source/opt/eliminate_dead_members_pass.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
+    "source/opt/fix_storage_class.cpp",
+    "source/opt/fix_storage_class.h",
     "source/opt/flatten_decoration_pass.cpp",
     "source/opt/flatten_decoration_pass.h",
     "source/opt/fold.cpp",
@@ -508,6 +519,8 @@
     "source/opt/freeze_spec_constant_value_pass.h",
     "source/opt/function.cpp",
     "source/opt/function.h",
+    "source/opt/generate_webgpu_initializers_pass.cpp",
+    "source/opt/generate_webgpu_initializers_pass.h",
     "source/opt/if_conversion.cpp",
     "source/opt/if_conversion.h",
     "source/opt/inline_exhaustive_pass.cpp",
@@ -530,6 +543,8 @@
     "source/opt/ir_loader.cpp",
     "source/opt/ir_loader.h",
     "source/opt/iterator.h",
+    "source/opt/legalize_vector_shuffle_pass.cpp",
+    "source/opt/legalize_vector_shuffle_pass.h",
     "source/opt/licm_pass.cpp",
     "source/opt/licm_pass.h",
     "source/opt/local_access_chain_convert_pass.cpp",
@@ -602,10 +617,14 @@
     "source/opt/set_spec_constant_default_value_pass.h",
     "source/opt/simplification_pass.cpp",
     "source/opt/simplification_pass.h",
+    "source/opt/split_invalid_unreachable_pass.cpp",
+    "source/opt/split_invalid_unreachable_pass.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_atomic_counter_memory_pass.cpp",
+    "source/opt/strip_atomic_counter_memory_pass.h",
     "source/opt/strip_debug_info_pass.cpp",
     "source/opt/strip_debug_info_pass.h",
     "source/opt/strip_reflect_info_pass.cpp",
@@ -636,147 +655,192 @@
     ":spvtools_headers",
   ]
 
-  configs -= [ "//build/config/compiler:chromium_code" ]
-  configs += [
-    "//build/config/compiler:no_chromium_code",
-    ":spvtools_internal_config",
-  ]
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+  configs += [ ":spvtools_internal_config" ]
 }
 
-group("SPIRV-Tools") {
+static_library("spvtools_link") {
+  sources = [
+    "source/link/linker.cpp",
+  ]
   deps = [
     ":spvtools",
     ":spvtools_opt",
     ":spvtools_val",
   ]
-}
-
-if (!build_with_chromium) {
-  googletest_dir = spirv_tools_googletest_dir
-
-  config("gtest_config") {
-    include_dirs = [
-      "${googletest_dir}/googletest",
-      "${googletest_dir}/googletest/include",
-    ]
-  }
-
-  static_library("gtest") {
-    testonly = true
-    sources = [
-      "${googletest_dir}/googletest/src/gtest-all.cc",
-    ]
-    public_configs = [ ":gtest_config" ]
-  }
-
-  config("gmock_config") {
-    include_dirs = [
-      "${googletest_dir}/googlemock",
-      "${googletest_dir}/googlemock/include",
-      "${googletest_dir}/googletest/include",
-    ]
-    if (is_clang) {
-      # TODO: Can remove this if/when the issue is fixed.
-      # https://github.com/google/googletest/issues/533
-      cflags = [ "-Wno-inconsistent-missing-override" ]
-    }
-  }
-
-  static_library("gmock") {
-    testonly = true
-    sources = [
-      "${googletest_dir}/googlemock/src/gmock-all.cc",
-    ]
-    public_configs = [ ":gmock_config" ]
-  }
-}
-
-test("spvtools_test") {
-  sources = [
-    "test/assembly_context_test.cpp",
-    "test/assembly_format_test.cpp",
-    "test/binary_destroy_test.cpp",
-    "test/binary_endianness_test.cpp",
-    "test/binary_header_get_test.cpp",
-    "test/binary_parse_test.cpp",
-    "test/binary_strnlen_s_test.cpp",
-    "test/binary_to_text.literal_test.cpp",
-    "test/binary_to_text_test.cpp",
-    "test/comment_test.cpp",
-    "test/enum_set_test.cpp",
-    "test/enum_string_mapping_test.cpp",
-    "test/ext_inst.debuginfo_test.cpp",
-    "test/ext_inst.glsl_test.cpp",
-    "test/ext_inst.opencl_test.cpp",
-    "test/fix_word_test.cpp",
-    "test/generator_magic_number_test.cpp",
-    "test/hex_float_test.cpp",
-    "test/immediate_int_test.cpp",
-    "test/libspirv_macros_test.cpp",
-    "test/name_mapper_test.cpp",
-    "test/named_id_test.cpp",
-    "test/opcode_make_test.cpp",
-    "test/opcode_require_capabilities_test.cpp",
-    "test/opcode_split_test.cpp",
-    "test/opcode_table_get_test.cpp",
-    "test/operand_capabilities_test.cpp",
-    "test/operand_pattern_test.cpp",
-    "test/operand_test.cpp",
-    "test/target_env_test.cpp",
-    "test/test_fixture.h",
-    "test/text_advance_test.cpp",
-    "test/text_destroy_test.cpp",
-    "test/text_literal_test.cpp",
-    "test/text_start_new_inst_test.cpp",
-    "test/text_to_binary.annotation_test.cpp",
-    "test/text_to_binary.barrier_test.cpp",
-    "test/text_to_binary.constant_test.cpp",
-    "test/text_to_binary.control_flow_test.cpp",
-    "test/text_to_binary.debug_test.cpp",
-    "test/text_to_binary.device_side_enqueue_test.cpp",
-    "test/text_to_binary.extension_test.cpp",
-    "test/text_to_binary.function_test.cpp",
-    "test/text_to_binary.group_test.cpp",
-    "test/text_to_binary.image_test.cpp",
-    "test/text_to_binary.literal_test.cpp",
-    "test/text_to_binary.memory_test.cpp",
-    "test/text_to_binary.misc_test.cpp",
-    "test/text_to_binary.mode_setting_test.cpp",
-    "test/text_to_binary.pipe_storage_test.cpp",
-    "test/text_to_binary.reserved_sampling_test.cpp",
-    "test/text_to_binary.subgroup_dispatch_test.cpp",
-    "test/text_to_binary.type_declaration_test.cpp",
-    "test/text_to_binary_test.cpp",
-    "test/text_word_get_test.cpp",
-    "test/unit_spirv.cpp",
-    "test/unit_spirv.h",
+  public_deps = [
+    ":spvtools_headers",
   ]
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+  configs += [ ":spvtools_internal_config" ]
+}
 
+static_library("spvtools_reduce") {
+  sources = [
+    "source/reduce/change_operand_reduction_opportunity.cpp",
+    "source/reduce/change_operand_reduction_opportunity.h",
+    "source/reduce/change_operand_to_undef_reduction_opportunity.cpp",
+    "source/reduce/change_operand_to_undef_reduction_opportunity.h",
+    "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp",
+    "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h",
+    "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp",
+    "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h",
+    "source/reduce/merge_blocks_reduction_opportunity.cpp",
+    "source/reduce/merge_blocks_reduction_opportunity.h",
+    "source/reduce/merge_blocks_reduction_opportunity_finder.cpp",
+    "source/reduce/merge_blocks_reduction_opportunity_finder.h",
+    "source/reduce/operand_to_const_reduction_opportunity_finder.cpp",
+    "source/reduce/operand_to_const_reduction_opportunity_finder.h",
+    "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp",
+    "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h",
+    "source/reduce/operand_to_undef_reduction_opportunity_finder.cpp",
+    "source/reduce/operand_to_undef_reduction_opportunity_finder.h",
+    "source/reduce/reducer.cpp",
+    "source/reduce/reducer.h",
+    "source/reduce/reduction_opportunity.cpp",
+    "source/reduce/reduction_opportunity.h",
+    "source/reduce/reduction_pass.cpp",
+    "source/reduce/reduction_pass.h",
+    "source/reduce/reduction_util.cpp",
+    "source/reduce/reduction_util.h",
+    "source/reduce/remove_block_reduction_opportunity.cpp",
+    "source/reduce/remove_block_reduction_opportunity.h",
+    "source/reduce/remove_block_reduction_opportunity_finder.cpp",
+    "source/reduce/remove_block_reduction_opportunity_finder.h",
+    "source/reduce/remove_function_reduction_opportunity.cpp",
+    "source/reduce/remove_function_reduction_opportunity.h",
+    "source/reduce/remove_function_reduction_opportunity_finder.cpp",
+    "source/reduce/remove_function_reduction_opportunity_finder.h",
+    "source/reduce/remove_instruction_reduction_opportunity.cpp",
+    "source/reduce/remove_instruction_reduction_opportunity.h",
+    "source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp",
+    "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h",
+    "source/reduce/remove_selection_reduction_opportunity.cpp",
+    "source/reduce/remove_selection_reduction_opportunity.h",
+    "source/reduce/remove_selection_reduction_opportunity_finder.cpp",
+    "source/reduce/remove_selection_reduction_opportunity_finder.h",
+    "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp",
+    "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h",
+    "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp",
+    "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h",
+    "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp",
+    "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h",
+    "source/reduce/structured_loop_to_selection_reduction_opportunity.cpp",
+    "source/reduce/structured_loop_to_selection_reduction_opportunity.h",
+    "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp",
+    "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h",
+    "source/spirv_reducer_options.cpp",
+    "source/spirv_reducer_options.h",
+  ]
   deps = [
     ":spvtools",
-    ":spvtools_language_header_unified1",
+    ":spvtools_opt",
+  ]
+  public_deps = [
+    ":spvtools_headers",
+  ]
+  if (build_with_chromium) {
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+  }
+  configs += [ ":spvtools_internal_config" ]
+}
+
+group("SPIRV-Tools") {
+  deps = [
+    ":spvtools",
+    ":spvtools_link",
+    ":spvtools_opt",
+    ":spvtools_reduce",
     ":spvtools_val",
   ]
+}
 
-  if (build_with_chromium) {
-    deps += [
+# The tests are scoped to Chromium to avoid needing to write gtest integration.
+# See Chromium's third_party/googletest/BUILD.gn for a complete integration.
+if (build_with_chromium) {
+  test("spvtools_test") {
+    sources = [
+      "test/assembly_context_test.cpp",
+      "test/assembly_format_test.cpp",
+      "test/binary_destroy_test.cpp",
+      "test/binary_endianness_test.cpp",
+      "test/binary_header_get_test.cpp",
+      "test/binary_parse_test.cpp",
+      "test/binary_strnlen_s_test.cpp",
+      "test/binary_to_text.literal_test.cpp",
+      "test/binary_to_text_test.cpp",
+      "test/comment_test.cpp",
+      "test/enum_set_test.cpp",
+      "test/enum_string_mapping_test.cpp",
+      "test/ext_inst.debuginfo_test.cpp",
+      "test/ext_inst.glsl_test.cpp",
+      "test/ext_inst.opencl_test.cpp",
+      "test/fix_word_test.cpp",
+      "test/generator_magic_number_test.cpp",
+      "test/hex_float_test.cpp",
+      "test/immediate_int_test.cpp",
+      "test/libspirv_macros_test.cpp",
+      "test/name_mapper_test.cpp",
+      "test/named_id_test.cpp",
+      "test/opcode_make_test.cpp",
+      "test/opcode_require_capabilities_test.cpp",
+      "test/opcode_split_test.cpp",
+      "test/opcode_table_get_test.cpp",
+      "test/operand_capabilities_test.cpp",
+      "test/operand_pattern_test.cpp",
+      "test/operand_test.cpp",
+      "test/target_env_test.cpp",
+      "test/test_fixture.h",
+      "test/text_advance_test.cpp",
+      "test/text_destroy_test.cpp",
+      "test/text_literal_test.cpp",
+      "test/text_start_new_inst_test.cpp",
+      "test/text_to_binary.annotation_test.cpp",
+      "test/text_to_binary.barrier_test.cpp",
+      "test/text_to_binary.constant_test.cpp",
+      "test/text_to_binary.control_flow_test.cpp",
+      "test/text_to_binary.debug_test.cpp",
+      "test/text_to_binary.device_side_enqueue_test.cpp",
+      "test/text_to_binary.extension_test.cpp",
+      "test/text_to_binary.function_test.cpp",
+      "test/text_to_binary.group_test.cpp",
+      "test/text_to_binary.image_test.cpp",
+      "test/text_to_binary.literal_test.cpp",
+      "test/text_to_binary.memory_test.cpp",
+      "test/text_to_binary.misc_test.cpp",
+      "test/text_to_binary.mode_setting_test.cpp",
+      "test/text_to_binary.pipe_storage_test.cpp",
+      "test/text_to_binary.reserved_sampling_test.cpp",
+      "test/text_to_binary.subgroup_dispatch_test.cpp",
+      "test/text_to_binary.type_declaration_test.cpp",
+      "test/text_to_binary_test.cpp",
+      "test/text_word_get_test.cpp",
+      "test/unit_spirv.cpp",
+      "test/unit_spirv.h",
+    ]
+
+    deps = [
+      ":spvtools",
+      ":spvtools_language_header_unified1",
+      ":spvtools_val",
       "//testing/gmock",
       "//testing/gtest",
       "//testing/gtest:gtest_main",
     ]
-  } else {
-    deps += [
-      ":gmock",
-      ":gtest",
-    ]
-    sources += [ "${googletest_dir}/googletest/src/gtest_main.cc" ]
-  }
 
-  if (is_clang) {
-    cflags_cc = [ "-Wno-self-assign" ]
-  }
+    if (is_clang) {
+      cflags_cc = [ "-Wno-self-assign" ]
+    }
 
-  configs += [ ":spvtools_internal_config" ]
+    configs += [ ":spvtools_internal_config" ]
+  }
 }
 
 if (spirv_tools_standalone) {
@@ -788,14 +852,129 @@
   }
 }
 
-executable("spirv-as") {
+source_set("spvtools_util_cli_consumer") {
+  sources = [
+    "tools/util/cli_consumer.cpp",
+    "tools/util/cli_consumer.h",
+  ]
+  configs += [ ":spvtools_internal_config" ]
+}
+
+source_set("spvtools_software_version") {
   sources = [
     "source/software_version.cpp",
+  ]
+  deps = [
+    ":spvtools_build_version",
+    ":spvtools_headers",
+  ]
+  configs += [ ":spvtools_internal_config" ]
+}
+
+executable("spirv-as") {
+  sources = [
     "tools/as/as.cpp",
   ]
   deps = [
     ":spvtools",
-    ":spvtools_build_version",
+    ":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" ]
+}
+
+if (!is_ios) {
+  # iOS does not allow std::system calls which spirv-reduce requires
+  executable("spirv-reduce") {
+    sources = [
+      "tools/reduce/reduce.cpp",
+    ]
+    deps = [
+      ":spvtools",
+      ":spvtools_opt",
+      ":spvtools_reduce",
+      ":spvtools_software_version",
+      ":spvtools_util_cli_consumer",
+      ":spvtools_val",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+}
+
+group("all_spirv_tools") {
+  deps = [
+    ":spirv-as",
+    ":spirv-cfg",
+    ":spirv-dis",
+    ":spirv-link",
+    ":spirv-opt",
+    ":spirv-val",
+  ]
+  if (!is_ios) {
+    deps += [ ":spirv-reduce" ]
+  }
+}
diff --git a/CHANGES b/CHANGES
index d4c6dff..11ecac5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,102 @@
 Revision history for SPIRV-Tools
 
-v2019.2-dev 2019-01-07
- - Start v2019.2-dev
+v2019.4-dev 2019-05-15
+ - Start v2019.4-dev
+
+v2019.3 2019-05-14
+ - General:
+   - Require Python 3 since Python 2 will out of service soon.
+   - Add a continuous test that does memory checks using the address sanitizer.
+   - Fix the build files so the SPIRV_USE_SANITIZER=address build works.
+   - Packaging top of tree build artifacts again.
+   - Added support for SPIR-V 1.4. (#2550)
+ - Optimizer
+   - Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
+   - Bindless Validation: Descriptor Initialization Check (#2419)
+   - Add option to validate after each pass (#2462)
+   - Add legalization pass to fix mismatched pointer (#2430, #2535)
+   - Add error messages when the input contains unknown instructions. (#2487)
+   - Add pass to convert from WebGPU Spir-V to Vulkan Spir-V and back. (#2495)
+   Fixes:
+   - #2412: Dead memeber elimination should not change input and output variables.
+   - #2405: Fix OpDot folding of half float vectors.
+   - #2391: Dead branch elim should not fold away back edges.
+   - #2441: Removing decorations when doing constant propagation.
+   - #2455: Maintain inst to block mapping in merge return.
+   - #2453: Fix merge return in the face of breaks.
+   - #2456: Handle dead infinite loops in DCE.
+   - #2458: Handle variable pointer in some optimizations.
+   - #2452: Fix dead branch elimination to handle unreachable blocks better.
+   - #2528: Fix undefined bit shift in sroa.
+   - #2539: Change implementation of post order CFG traversal.
+ - Validator
+   - Add validation of storage classes for WebGPU (#2446)
+   - Add validation for ExecutionMode in WebGPU (#2443)
+   - Implement WebGPU specific CFG validation (#2386)
+   - Allow NonWritable to target struct members. (#2420)
+   - Allow storage type mismatch for parameter in relaxed addressing mode.
+   - Allow non memory objects as parameter in relaxed addressing mode.
+   - Disallow nested Blocks and buffer blocks (#2410).
+   - Add validation for SPV_NV_cooperative_matrix (#2404)
+   - Add --strip-atomic-counter-memory (#2413)
+   - Check OpSampledImage is only passed into valid instructions (#2467)
+   - Handle function decls in Structured CFG analysis (#2474)
+   - Validate that OpUnreacahble is not statically reachable (#2473)
+   - Add pass to generate needed initializers for WebGPU (#2481)
+   - Allow images without format for OpenCL. (#2470)
+   - Remove unreachable block validation (#2525)
+   - Reduce runtime of array layout checks (#2534)
+   - Add validation specific to OpExecutionModeId (#2536)
+   - Validate sign of int types. (#2549)
+   - VK_KHR_uniform_buffer_standard_layout validation (#2562)
+   Fixes:
+   - #2439: Add missing DepthGreater case to Fragment only check.
+   - #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan.
+   - #2408: Restrict and Aliased decorations cannot be applied to the same id.
+   - #2447: Improve function call parameter check.
+ - Reduce
+   - Add Pass to remove unreferenced blocks. (#2398)
+   - Allows passing options to the validator. (#2401)
+   - Improve reducer algorithm and other changes (#2472)
+   - Add Pass to remove selections (#2485)
+   - Add passes to simplify branches (#2507)
+   Fixes:
+   - #2478: fix loop to selection pass for loops with combined header/continue block
+
+v2019.2 2019-02-20
+ - General:
+   - Support SPV_EXT_physical_storage_buffer
+   - A number of memory leak have been fixed.
+   - Removed use of deprecated Google test macro:
+   - Changed the BUILD.gn to only build tests in Chromium.
+ - Optimizer
+   - Upgrade memory model improvments for modf and frexp.
+   - Add a new pass to move loads closer to their uses: code sinking.
+   - Invalidating the type manager now invalidates the constnat manager.
+   - Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays.
+   - Add a new pass that removes members from structs that are not used: dead member elimination.
+   Fixes:
+   - #2292: Remove undefined behaviour when folding bit shifts.
+   - #2294: Fixes for instrumentation code.
+   - #2293: Fix overflow when folding -INT_MIN.
+   - #2374: Don't merge unreachable blocks when merging blocks.
+ - Validator
+   - Support SPV_KHR_no_integer_wrap and related decorations.
+   - Validate Vulkan rules for OpTypeRuntimeArray.
+   - Validate NonWritable decoration.
+   - Many WebGPU specific validation rules were added.
+   - Validate variable pointer related function call rules.
+   - Better error messages.
+   Fixes:
+   - #2307: Check forwards references in OpTypeArray.
+   - #2315, #2303: Fixed the layout check for relaxed layout.
+   - #1628: Emit an error when an OpSwitch target is not an OpLabel.
+ - Reduce
+   - Added more documentation for spirv-reduce.
+   - Add ability to remove OpPhi instructions.
+   - Add ability to merge two basic blocks.
+   - Add ability to remove unused functions and unused basic blocks.
+   Fixes:
 
 v2019.1 2019-01-07
  - General:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a5ecb90..9f24e38 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -69,6 +69,10 @@
 endif()
 
 option(SPIRV_BUILD_COMPRESSION "Build SPIR-V compressing codec" OFF)
+if(SPIRV_BUILD_COMPRESSION)
+  message(FATAL_ERROR "SPIR-V compression codec has been removed from SPIR-V tools. "
+          "Please remove SPIRV_BUILD_COMPRESSION from your build options.")
+endif(SPIRV_BUILD_COMPRESSION)
 
 option(SPIRV_WERROR "Enable error on warning" ON)
 if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
@@ -140,6 +144,8 @@
       if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
         target_compile_options(${TARGET} PRIVATE
           -fsanitize=${SPIRV_USE_SANITIZER})
+        set_target_properties(${TARGET} PROPERTIES
+          LINK_FLAGS -fsanitize=${SPIRV_USE_SANITIZER})
       endif()
       target_compile_options(${TARGET} PRIVATE
          -ftemplate-depth=1024)
@@ -176,7 +182,8 @@
   endmacro()
 endif()
 
-find_host_package(PythonInterp)
+# Tests require Python3
+find_host_package(PythonInterp 3 REQUIRED)
 
 # Check for symbol exports on Linux.
 # At the moment, this check will fail on the OSX build machines for the Android NDK.
@@ -254,9 +261,6 @@
 
 set(SPIRV_LIBRARIES "-lSPIRV-Tools -lSPIRV-Tools-link -lSPIRV-Tools-opt")
 set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared")
-if(SPIRV_BUILD_COMPRESSION)
-  set(SPIRV_LIBRARIES "${SPIRV_LIBRARIES} -lSPIRV-Tools-comp")
-endif(SPIRV_BUILD_COMPRESSION)
 
 # Build pkg-config file
 # Use a first-class target so it's regenerated when relevant files are updated.
diff --git a/DEPS b/DEPS
index 5668c66..70685ad 100644
--- a/DEPS
+++ b/DEPS
@@ -1,174 +1,26 @@
 use_relative_paths = True
 
 vars = {
-  'chromium_git': 'https://chromium.googlesource.com',
   'github': 'https://github.com',
 
-  'build_revision': '037f38ae0fe5e11b4f7c33b750fd7a1e9634a606',
-  'buildtools_revision': 'ab7b6a7b350dd15804c87c20ce78982811fdd76f',
-  'clang_revision': 'abe5e4f9dc0f1df848c7a0efa05256253e77a7b7',
-  'effcee_revision': '04b624799f5a9dbaf3fa1dbed2ba9dce2fc8dcf2',
-  'googletest_revision': '98a0d007d7092b72eea0e501bb9ad17908a1a036',
-  'testing_revision': '340252637e2e7c72c0901dcbeeacfff419e19b59',
-  're2_revision': '6cf8ccd82dbaab2668e9b13596c68183c9ecd13f',
-  'spirv_headers_revision': '79b6681aadcb53c27d1052e5f8a0e82a981dbf2f',
+  'effcee_revision': 'b83b58d177b797edd1f94c5f10837f2cc2863f0a',
+  'googletest_revision': '2f42d769ad1b08742f7ccb5ad4dd357fc5ff248c',
+  're2_revision': 'e356bd3f80e0c15c1050323bb5a2d0f8ea4845f4',
+  'spirv_headers_revision': '123dc278f204f8e833e1a88d31c46d0edf81d4b2',
 }
 
 deps = {
-  "build":
-    Var('chromium_git') + "/chromium/src/build.git@" + Var('build_revision'),
-
-  'buildtools':
-      Var('chromium_git') + '/chromium/buildtools.git@' +
-          Var('buildtools_revision'),
-
-  'external/spirv-headers':
-      Var('github') +  '/KhronosGroup/SPIRV-Headers.git@' +
-          Var('spirv_headers_revision'),
+  'external/effcee':
+      Var('github') + '/google/effcee.git@' + Var('effcee_revision'),
 
   'external/googletest':
       Var('github') + '/google/googletest.git@' + Var('googletest_revision'),
 
-  'external/effcee':
-      Var('github') + '/google/effcee.git@' + Var('effcee_revision'),
-
   'external/re2':
       Var('github') + '/google/re2.git@' + Var('re2_revision'),
 
-  'testing':
-      Var('chromium_git') + '/chromium/src/testing@' +
-          Var('testing_revision'),
-
-  'tools/clang':
-      Var('chromium_git') + '/chromium/src/tools/clang@' + Var('clang_revision')
+  'external/spirv-headers':
+      Var('github') +  '/KhronosGroup/SPIRV-Headers.git@' +
+          Var('spirv_headers_revision'),
 }
 
-recursedeps = [
-  # buildtools provides clang_format, libc++, and libc++api
-  'buildtools',
-]
-
-hooks = [
-  {
-    'name': 'gn_win',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=win32',
-                '--no_auth',
-                '--bucket', 'chromium-gn',
-                '-s', 'SPIRV-Tools/buildtools/win/gn.exe.sha1',
-    ],
-  },
-  {
-    'name': 'gn_mac',
-    'pattern': '.',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=darwin',
-                '--no_auth',
-                '--bucket', 'chromium-gn',
-                '-s', 'SPIRV-Tools/buildtools/mac/gn.sha1',
-    ],
-  },
-  {
-    'name': 'gn_linux64',
-    'pattern': '.',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=linux*',
-                '--no_auth',
-                '--bucket', 'chromium-gn',
-                '-s', 'SPIRV-Tools/buildtools/linux64/gn.sha1',
-    ],
-  },
-  # Pull clang-format binaries using checked-in hashes.
-  {
-    'name': 'clang_format_win',
-    'pattern': '.',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=win32',
-                '--no_auth',
-                '--bucket', 'chromium-clang-format',
-                '-s', 'SPIRV-Tools/buildtools/win/clang-format.exe.sha1',
-    ],
-  },
-  {
-    'name': 'clang_format_mac',
-    'pattern': '.',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=darwin',
-                '--no_auth',
-                '--bucket', 'chromium-clang-format',
-                '-s', 'SPIRV-Tools/buildtools/mac/clang-format.sha1',
-    ],
-  },
-  {
-    'name': 'clang_format_linux',
-    'pattern': '.',
-    'action': [ 'download_from_google_storage',
-                '--no_resume',
-                '--platform=linux*',
-                '--no_auth',
-                '--bucket', 'chromium-clang-format',
-                '-s', 'SPIRV-Tools/buildtools/linux64/clang-format.sha1',
-    ],
-  },
-  {
-    # Pull clang
-    'name': 'clang',
-    'pattern': '.',
-    'action': ['python',
-               'SPIRV-Tools/tools/clang/scripts/update.py'
-    ],
-  },
-  {
-    'name': 'sysroot_arm',
-    'pattern': '.',
-    'condition': 'checkout_linux and checkout_arm',
-    'action': ['python', 'SPIRV-Tools/build/linux/sysroot_scripts/install-sysroot.py',
-               '--arch=arm'],
-  },
-  {
-    'name': 'sysroot_arm64',
-    'pattern': '.',
-    'condition': 'checkout_linux and checkout_arm64',
-    'action': ['python', 'SPIRV-Tools/build/linux/sysroot_scripts/install-sysroot.py',
-               '--arch=arm64'],
-  },
-  {
-    'name': 'sysroot_x86',
-    'pattern': '.',
-    'condition': 'checkout_linux and (checkout_x86 or checkout_x64)',
-    'action': ['python', 'SPIRV-Tools/build/linux/sysroot_scripts/install-sysroot.py',
-               '--arch=x86'],
-  },
-  {
-    'name': 'sysroot_mips',
-    'pattern': '.',
-    'condition': 'checkout_linux and checkout_mips',
-    'action': ['python', 'SPIRV-Tools/build/linux/sysroot_scripts/install-sysroot.py',
-               '--arch=mips'],
-  },
-  {
-    'name': 'sysroot_x64',
-    'pattern': '.',
-    'condition': 'checkout_linux and checkout_x64',
-    'action': ['python', 'SPIRV-Tools/build/linux/sysroot_scripts/install-sysroot.py',
-               '--arch=x64'],
-  },
-  {
-    # Update the Windows toolchain if necessary.
-    'name': 'win_toolchain',
-    'pattern': '.',
-    'condition': 'checkout_win',
-    'action': ['python', 'SPIRV-Tools/build/vs_toolchain.py', 'update', '--force'],
-  },
-  {
-    # Update the Mac toolchain if necessary.
-    'name': 'mac_toolchain',
-    'pattern': '.',
-    'action': ['python', 'SPIRV-Tools/build/mac_toolchain.py'],
-  },
-]
diff --git a/README.md b/README.md
index 534664f..8a5c85e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,5 @@
 # SPIR-V Tools
 
-[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
-<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_release.svg)
-<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_release.svg)
-<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)
-
 ## Overview
 
 The SPIR-V Tools project provides an API and commands for processing SPIR-V
@@ -24,6 +19,15 @@
 See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification,
 headers, and XML registry.
 
+## Downloads
+
+[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
+<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html)
+<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html)
+<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html)
+
+[More downloads](downloads.md)
+
 ## Versioning SPIRV-Tools
 
 See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.
@@ -47,7 +51,7 @@
 
 * Support for SPIR-V 1.0, 1.1, 1.2, and 1.3
   * Based on SPIR-V syntax described by JSON grammar files in the
-    [SPIRV-Headers](spirv-headers) repository.
+    [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository.
 * Support for extended instruction sets:
   * GLSL std450 version 1.0 Rev 3
   * OpenCL version 1.0 Rev 2
@@ -128,6 +132,23 @@
 sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
 planned and in-progress work.
 
+
+### Reducer
+
+*Note:* The reducer is still under development.
+
+The reducer simplifies and shrinks a SPIR-V module with respect to a
+user-supplied *interestingness function*.  For example, given a large
+SPIR-V module that cause some SPIR-V compiler to fail with a given
+fatal error message, the reducer could be used to look for a smaller
+version of the module that causes the compiler to fail with the same
+fatal error message.
+
+To suggest an additional capability for the reducer, [file an
+issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
+"Reducer:" as the start of its title.
+
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -249,9 +270,38 @@
 cmake [-G <platform-generator>] <spirv-dir>
 ```
 
+*Note*:
+The script `utils/git-sync-deps` can be used to checkout and/or update the
+contents of the repos under `external/` instead of manually maintaining them.
+
 Once the build files have been generated, build using your preferred
 development environment.
 
+### Tools you'll need
+
+For building and testing SPIRV-Tools, the following tools should be
+installed regardless of your OS:
+
+- [CMake](http://www.cmake.org/): for generating compilation targets.  Version
+  2.8.12 or later.
+- [Python 3](http://www.python.org/): for utility scripts and running the test
+suite.
+
+SPIRV-Tools is regularly tested with the the following compilers:
+
+On Linux
+- GCC version 4.8.5
+- Clang version 3.8
+
+On MacOS
+- AppleClang 10.0
+
+On Windows
+- Visual Studio 2015
+- Visual Studio 2017
+
+Other compilers or later versions may work, but they are not tested.
+
 ### CMake options
 
 The following CMake options are supported:
@@ -261,8 +311,6 @@
   the command line tools.  This will prevent the tests from being built.
 * `SPIRV_SKIP_EXECUTABLES={ON|OFF}`, default `OFF`- Build only the library, not
   the command line tools and tests.
-* `SPIRV_BUILD_COMPRESSION={ON|OFF}`, default `OFF`- Build SPIR-V compressing
-  codec.
 * `SPIRV_USE_SANITIZER=<sanitizer>`, default is no sanitizing - On UNIX
   platforms with an appropriate version of `clang` this option enables the use
   of the sanitizers documented [here][clang-sanitizers].
@@ -300,6 +348,13 @@
                       NDK_APP_OUT=`pwd`/app
 ```
 
+### Updating DEPS
+Occasionally the entries in DEPS will need to be updated. This is done on demand
+when there is a request to do this, often due to downstream breakages. There is
+a script `utils/roll_deps.sh` provided, which will generate a patch with the
+updated DEPS values. This will still need to be tested in your checkout to
+confirm that there are no integration issues that need to be resolved.
+
 ## Library
 
 ### Usage
@@ -415,6 +470,18 @@
 * `spirv-val` - the standalone validator
   * `<spirv-dir>/tools/val`
 
+### Reducer tool
+
+The reducer shrinks a SPIR-V binary module, guided by a user-supplied
+*interestingness test*.
+
+This is a work in progress, with initially only shrinks a module in a few ways.
+
+* `spirv-reduce` - the standalone reducer
+  * `<spirv-dir>/tools/reduce`
+
+Run `spirv-reduce --help` to see how to specify interestingness.
+
 ### Control flow dumper tool
 
 The control flow dumper prints the control flow graph for a SPIR-V module as a
diff --git a/downloads.md b/downloads.md
new file mode 100644
index 0000000..9c7d856
--- /dev/null
+++ b/downloads.md
@@ -0,0 +1,14 @@
+# Downloads
+Download the latest builds.
+
+## Release
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | |
+
+## Debug
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | |
diff --git a/examples/cpp-interface/main.cpp b/examples/cpp-interface/main.cpp
index c5354b8..a1e22c7 100644
--- a/examples/cpp-interface/main.cpp
+++ b/examples/cpp-interface/main.cpp
@@ -28,6 +28,7 @@
 
 int main() {
   const std::string source =
+      "         OpCapability Linkage "
       "         OpCapability Shader "
       "         OpMemoryModel Logical GLSL450 "
       "         OpSource GLSL 450 "
@@ -36,8 +37,8 @@
       " %spec = OpSpecConstant %int 0 "
       "%const = OpConstant %int 42";
 
-  spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0);
-  spvtools::Optimizer opt(SPV_ENV_VULKAN_1_0);
+  spvtools::SpirvTools core(SPV_ENV_UNIVERSAL_1_3);
+  spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_3);
 
   auto print_msg_to_stderr = [](spv_message_level_t, const char*,
                                 const spv_position_t&, const char* m) {
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index d1251c2..3190f4b 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -102,3 +102,15 @@
     endif()
   endif()
 endif()
+
+if(SPIRV_BUILD_FUZZER)
+  set(PROTOBUF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake)
+  set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
+  set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
+  if (IS_DIRECTORY ${PROTOBUF_DIR})
+    add_subdirectory(${PROTOBUF_DIR} EXCLUDE_FROM_ALL)
+  else()
+    message(FATAL_ERROR
+      "protobuf not found - please checkout a copy under external/.")
+  endif()
+endif(SPIRV_BUILD_FUZZER)
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index f806809..dfd6e35 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -30,17 +30,22 @@
 
 // Stream Output Buffer Offsets
 //
-// The following values provide 32-bit word offsets into the output buffer
+// The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
 // by InstBindlessCheckPass.
 //
-// The first word of the debug output buffer contains the next available word
+// kInst2* values support version 2 of the output record format. These should
+// be used if available and version 2 is enabled. Version 1 is DEPRECATED.
+// Specifically, version 1 uses two words for the stage-specific section of
+// the output record; version 2 uses three words.
+//
+// The first member of the debug output buffer contains the next available word
 // in the data stream to be written. Shaders will atomically read and update
 // this value so as not to overwrite each others records. This value must be
 // initialized to zero
 static const int kDebugOutputSizeOffset = 0;
 
-// The second word of the output buffer is the start of the stream of records
+// The second member of the output buffer is the start of the stream of records
 // written by the instrumented shaders. Each record represents a validation
 // error. The format of the records is documented below.
 static const int kDebugOutputDataOffset = 1;
@@ -70,38 +75,63 @@
 
 // Stage-specific Stream Record Offsets
 //
-// Each stage will contain different values in the next two words of the record
-// used to identify which instantiation of the shader generated the validation
-// error.
+// Each stage will contain different values in the next set of words of the
+// record used to identify which instantiation of the shader generated the
+// validation error.
 //
 // Vertex Shader Output Record Offsets
 static const int kInstVertOutVertexIndex = kInstCommonOutCnt;
 static const int kInstVertOutInstanceIndex = kInstCommonOutCnt + 1;
+static const int kInstVertOutUnused = kInstCommonOutCnt + 2;
 
 // Frag Shader Output Record Offsets
 static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
 static const int kInstFragOutFragCoordY = kInstCommonOutCnt + 1;
+static const int kInstFragOutUnused = kInstCommonOutCnt + 2;
 
 // Compute Shader Output Record Offsets
+static const int kInstCompOutGlobalInvocationIdX = kInstCommonOutCnt;
+static const int kInstCompOutGlobalInvocationIdY = kInstCommonOutCnt + 1;
+static const int kInstCompOutGlobalInvocationIdZ = kInstCommonOutCnt + 2;
+
+// Compute Shader Output Record Offsets - Version 1 (DEPRECATED)
 static const int kInstCompOutGlobalInvocationId = kInstCommonOutCnt;
 static const int kInstCompOutUnused = kInstCommonOutCnt + 1;
 
-// Tessellation Shader Output Record Offsets
+// Tessellation Control Shader Output Record Offsets
+static const int kInstTessCtlOutInvocationId = kInstCommonOutCnt;
+static const int kInstTessCtlOutPrimitiveId = kInstCommonOutCnt + 1;
+static const int kInstTessCtlOutUnused = kInstCommonOutCnt + 2;
+
+// Tessellation Eval Shader Output Record Offsets
+static const int kInstTessEvalOutPrimitiveId = kInstCommonOutCnt;
+static const int kInstTessEvalOutTessCoordU = kInstCommonOutCnt + 1;
+static const int kInstTessEvalOutTessCoordV = kInstCommonOutCnt + 2;
+
+// Tessellation Shader Output Record Offsets - Version 1 (DEPRECATED)
 static const int kInstTessOutInvocationId = kInstCommonOutCnt;
 static const int kInstTessOutUnused = kInstCommonOutCnt + 1;
 
 // Geometry Shader Output Record Offsets
 static const int kInstGeomOutPrimitiveId = kInstCommonOutCnt;
 static const int kInstGeomOutInvocationId = kInstCommonOutCnt + 1;
+static const int kInstGeomOutUnused = kInstCommonOutCnt + 2;
+
+// Ray Tracing Shader Output Record Offsets
+static const int kInstRayTracingOutLaunchIdX = kInstCommonOutCnt;
+static const int kInstRayTracingOutLaunchIdY = kInstCommonOutCnt + 1;
+static const int kInstRayTracingOutLaunchIdZ = kInstCommonOutCnt + 2;
 
 // Size of Common and Stage-specific Members
 static const int kInstStageOutCnt = kInstCommonOutCnt + 2;
+static const int kInst2StageOutCnt = kInstCommonOutCnt + 3;
 
-// Validation Error Code
+// Validation Error Code Offset
 //
 // This identifies the validation error. It also helps to identify
 // how many words follow in the record and their meaning.
 static const int kInstValidationOutError = kInstStageOutCnt;
+static const int kInst2ValidationOutError = kInst2StageOutCnt;
 
 // Validation-specific Output Record Offsets
 //
@@ -110,26 +140,82 @@
 // about the validation error.
 //
 // A bindless bounds error will output the index and the bound.
+static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+
+static const int kInst2BindlessBoundsOutDescIndex = kInst2StageOutCnt + 1;
+static const int kInst2BindlessBoundsOutDescBound = kInst2StageOutCnt + 2;
+static const int kInst2BindlessBoundsOutCnt = kInst2StageOutCnt + 3;
+
+// A bindless uninitialized error will output the index.
+static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+
+static const int kInst2BindlessUninitOutDescIndex = kInst2StageOutCnt + 1;
+static const int kInst2BindlessUninitOutUnused = kInst2StageOutCnt + 2;
+static const int kInst2BindlessUninitOutCnt = kInst2StageOutCnt + 3;
+
+// DEPRECATED
 static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
 static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
 
 // Maximum Output Record Member Count
 static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
+static const int kInst2MaxOutCnt = kInst2StageOutCnt + 3;
 
 // Validation Error Codes
 //
 // These are the possible validation error codes.
 static const int kInstErrorBindlessBounds = 0;
+static const int kInstErrorBindlessUninit = 1;
+
+// Direct Input Buffer Offsets
+//
+// The following values provide member offsets into the input buffers
+// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
+// by InstBindlessCheckPass.
+//
+// The only object in an input buffer is a runtime array of unsigned
+// integers. Each validation will have its own formatting of this array.
+static const int kDebugInputDataOffset = 0;
 
 // Debug Buffer Bindings
 //
 // These are the bindings for the different buffers which are
 // read or written by the instrumentation passes.
 //
-// This is the output buffer written by InstBindlessCheckPass.
+// This is the output buffer written by InstBindlessCheckPass
+// and possibly other future validations.
 static const int kDebugOutputBindingStream = 0;
 
+// The binding for the input buffer read by InstBindlessCheckPass and
+// possibly other future validations.
+static const int kDebugInputBindingBindless = 1;
+
+// Bindless Validation Input Buffer Format
+//
+// An input buffer for bindless validation consists of a single array of
+// unsigned integers we will call Data[]. This array is formatted as follows.
+//
+// At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
+// gives an offset to the start of the bindless initialization data. More
+// specifically, if the following value is zero, we know that the descriptor at
+// (set = s, binding = b, index = i) is not initialized:
+// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
+static const int kDebugInputBindlessInitOffset = 0;
+
+// DEPRECATED
+static const int kDebugInputBindlessOffsetReserved = 0;
+
+// At offset kDebugInputBindlessOffsetLengths is some number of uints which
+// provide the bindless length data. More specifically, the number of
+// descriptors at (set=s, binding=b) is:
+// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
+static const int kDebugInputBindlessOffsetLengths = 1;
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index ff7eb6b..e21b058 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -370,6 +370,8 @@
 
 typedef struct spv_reducer_options_t spv_reducer_options_t;
 
+typedef struct spv_fuzzer_options_t spv_fuzzer_options_t;
+
 // Type Definitions
 
 typedef spv_const_binary_t* spv_const_binary;
@@ -385,6 +387,8 @@
 typedef const spv_optimizer_options_t* spv_const_optimizer_options;
 typedef spv_reducer_options_t* spv_reducer_options;
 typedef const spv_reducer_options_t* spv_const_reducer_options;
+typedef spv_fuzzer_options_t* spv_fuzzer_options;
+typedef const spv_fuzzer_options_t* spv_const_fuzzer_options;
 
 // Platform API
 
@@ -427,6 +431,8 @@
   SPV_ENV_UNIVERSAL_1_3,  // SPIR-V 1.3 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_1,     // Vulkan 1.1 latest revision.
   SPV_ENV_WEBGPU_0,       // Work in progress WebGPU 1.0.
+  SPV_ENV_UNIVERSAL_1_4,  // SPIR-V 1.4 latest revision, no other restrictions.
+  SPV_ENV_VULKAN_1_1_SPIRV_1_4,  // Vulkan 1.1 with SPIR-V 1.4 binary.
 } spv_target_env;
 
 // SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -445,6 +451,10 @@
 // Returns a string describing the given SPIR-V target environment.
 SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
 
+// Parses s into *env and returns true if successful.  If unparsable, returns
+// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
+SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
+
 // Creates a context object.  Returns null if env is invalid.
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
@@ -488,6 +498,20 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxLogicalPointer(
     spv_validator_options options, bool val);
 
+// Records whether or not the validator should relax the rules because it is
+// expected that the optimizations will make the code legal.
+//
+// When relaxed, it will allow the following:
+// 1) It will allow relaxed logical pointers.  Setting this option will also
+//    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
+//    to the same type pointed as the formal parameter.  The types just need to
+//    logically match.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetBeforeHlslLegalization(
+    spv_validator_options options, bool val);
+
 // Records whether the validator should use "relaxed" block layout rules.
 // Relaxed layout rules are described by Vulkan extension
 // VK_KHR_relaxed_block_layout, and they affect uniform blocks, storage blocks,
@@ -498,6 +522,11 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether the validator should use standard block layout rules for
+// uniform blocks.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout(
+    spv_validator_options options, bool val);
+
 // Records whether the validator should use "scalar" block layout rules.
 // Scalar layout rules are more permissive than relaxed block layout.
 //
@@ -545,6 +574,15 @@
 SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetMaxIdBound(
     spv_optimizer_options options, uint32_t val);
 
+// Records whether all bindings within the module should be preserved.
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings(
+    spv_optimizer_options options, bool val);
+
+// Records whether all specialization constants within the module
+// should be preserved.
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants(
+    spv_optimizer_options options, bool val);
+
 // Creates a reducer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvReducerOptionsDestroy|.
@@ -553,14 +591,35 @@
 // Destroys the given reducer options object.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
 
-// Records the maximum number of reduction steps that should run before the
-// reducer gives up.
+// Sets the maximum number of reduction steps that should run before the reducer
+// gives up.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
     spv_reducer_options options, uint32_t step_limit);
 
-// Sets seed for random number generation.
-SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
-                                                 uint32_t seed);
+// Sets the fail-on-validation-error option; if true, the reducer will return
+// kStateInvalid if a reduction step yields a state that fails SPIR-V
+// validation. Otherwise, an invalid state is treated as uninteresting and the
+// reduction backtracks and continues.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+    spv_reducer_options options, bool fail_on_validation_error);
+
+// Creates a fuzzer options object with default options. Returns a valid
+// options object. The object remains valid until it is passed into
+// |spvFuzzerOptionsDestroy|.
+SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate();
+
+// Destroys the given fuzzer options object.
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsDestroy(spv_fuzzer_options options);
+
+// Sets the seed with which the random number generator used by the fuzzer
+// should be initialized.
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
+    spv_fuzzer_options options, uint32_t seed);
+
+// Sets the maximum number of steps that the shrinker should take before giving
+// up.
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
+    spv_fuzzer_options options, uint32_t shrinker_step_limit);
 
 // Encodes the given SPIR-V assembly text to its binary representation. The
 // length parameter specifies the number of bytes for text. Encoded binary will
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 9cb5afe..2da1152 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -88,6 +88,12 @@
     spvValidatorOptionsSetRelaxBlockLayout(options_, val);
   }
 
+  // Enables VK_KHR_uniform_buffer_standard_layout when validating standard
+  // uniform layout.  If true, disables scalar block layout rules.
+  void SetUniformBufferStandardLayout(bool val) {
+    spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
+  }
+
   // Enables VK_EXT_scalar_block_layout when validating standard
   // uniform/storage buffer/push-constant layout.  If true, disables
   // relaxed block layout rules.
@@ -110,6 +116,21 @@
     spvValidatorOptionsSetRelaxLogicalPointer(options_, val);
   }
 
+  // Records whether or not the validator should relax the rules because it is
+  // expected that the optimizations will make the code legal.
+  //
+  // When relaxed, it will allow the following:
+  // 1) It will allow relaxed logical pointers.  Setting this option will also
+  //    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 to the same type pointed as the formal parameter.  The types just
+  //    need to logically match.
+  void SetBeforeHlslLegalization(bool val) {
+    spvValidatorOptionsSetBeforeHlslLegalization(options_, val);
+  }
+
  private:
   spv_validator_options options_;
 };
@@ -140,6 +161,18 @@
     spvOptimizerOptionsSetMaxIdBound(options_, new_bound);
   }
 
+  // Records whether all bindings within the module should be preserved.
+  void set_preserve_bindings(bool preserve_bindings) {
+    spvOptimizerOptionsSetPreserveBindings(options_, preserve_bindings);
+  }
+
+  // Records whether all specialization constants within the module
+  // should be preserved.
+  void set_preserve_spec_constants(bool preserve_spec_constants) {
+    spvOptimizerOptionsSetPreserveSpecConstants(options_,
+                                                preserve_spec_constants);
+  }
+
  private:
   spv_optimizer_options options_;
 };
@@ -151,21 +184,50 @@
   ~ReducerOptions() { spvReducerOptionsDestroy(options_); }
 
   // Allow implicit conversion to the underlying object.
-  operator spv_reducer_options() const { return options_; }
+  operator spv_reducer_options() const {  // NOLINT(google-explicit-constructor)
+    return options_;
+  }
 
-  // Records the maximum number of reduction steps that should
-  // run before the reducer gives up.
+  // See spvReducerOptionsSetStepLimit.
   void set_step_limit(uint32_t step_limit) {
     spvReducerOptionsSetStepLimit(options_, step_limit);
   }
 
-  // Sets a seed to be used for random number generation.
-  void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
+  // See spvReducerOptionsSetFailOnValidationError.
+  void set_fail_on_validation_error(bool fail_on_validation_error) {
+    spvReducerOptionsSetFailOnValidationError(options_,
+                                              fail_on_validation_error);
+  }
 
  private:
   spv_reducer_options options_;
 };
 
+// A C++ wrapper around a fuzzer options object.
+class FuzzerOptions {
+ public:
+  FuzzerOptions() : options_(spvFuzzerOptionsCreate()) {}
+  ~FuzzerOptions() { spvFuzzerOptionsDestroy(options_); }
+
+  // Allow implicit conversion to the underlying object.
+  operator spv_fuzzer_options() const {  // NOLINT(google-explicit-constructor)
+    return options_;
+  }
+
+  // See spvFuzzerOptionsSetRandomSeed.
+  void set_random_seed(uint32_t seed) {
+    spvFuzzerOptionsSetRandomSeed(options_, seed);
+  }
+
+  // See spvFuzzerOptionsSetShrinkerStepLimit.
+  void set_shrinker_step_limit(uint32_t shrinker_step_limit) {
+    spvFuzzerOptionsSetShrinkerStepLimit(options_, shrinker_step_limit);
+  }
+
+ private:
+  spv_fuzzer_options options_;
+};
+
 // C++ interface for SPIRV-Tools functionalities. It wraps the context
 // (including target environment and the corresponding SPIR-V grammar) and
 // provides methods for assembling, disassembling, and validating.
@@ -235,6 +297,9 @@
   bool Validate(const uint32_t* binary, size_t binary_size,
                 spv_validator_options options) const;
 
+  // Was this object successfully constructed.
+  bool IsValid() const;
+
  private:
   struct Impl;  // Opaque struct for holding the data fields used by this class.
   std::unique_ptr<Impl> impl_;  // Unique pointer to implementation data.
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index b59329d..4c668b4 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -101,10 +101,15 @@
   // from time to time.
   Optimizer& RegisterSizePasses();
 
-  // Registers passes that have been prescribed for WebGPU environments.
-  // This sequence of passes is subject to constant review and will change
-  // from time to time.
-  Optimizer& RegisterWebGPUPasses();
+  // Registers passes that have been prescribed for converting from Vulkan to
+  // WebGPU. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterVulkanToWebGPUPasses();
+
+  // Registers passes that have been prescribed for converting from WebGPU to
+  // Vulkan. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterWebGPUToVulkanPasses();
 
   // Registers passes that attempt to legalize the generated code.
   //
@@ -199,6 +204,9 @@
   // |out| output stream.
   Optimizer& SetTimeReport(std::ostream* out);
 
+  // Sets the option to validate the module after each pass.
+  Optimizer& SetValidateAfterAll(bool validate);
+
  private:
   struct Impl;                  // Opaque struct for holding internal data.
   std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
@@ -208,6 +216,13 @@
 // A null pass does nothing to the SPIR-V module to be optimized.
 Optimizer::PassToken CreateNullPass();
 
+// Creates a strip-atomic-counter-memory pass.
+// A strip-atomic-counter-memory pass removes all usages of the
+// AtomicCounterMemory bit in Memory Semantics bitmasks. This bit is a no-op in
+// Vulkan, so isn't needed in that env. And the related capability is not
+// allowed in WebGPU, so it is not allowed in that env.
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass();
+
 // Creates a strip-debug-info pass.
 // A strip-debug-info pass removes all debug instructions (as documented in
 // Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
@@ -226,6 +241,11 @@
 // functions are not needed because they will never be called.
 Optimizer::PassToken CreateEliminateDeadFunctionsPass();
 
+// Creates an eliminate-dead-members pass.
+// An eliminate-dead-members pass will remove all unused members of structures.
+// This will not affect the data layout of the remaining members.
+Optimizer::PassToken CreateEliminateDeadMembersPass();
+
 // Creates a set-spec-constant-default-value pass from a mapping from spec-ids
 // to the default values in the form of string.
 // A set-spec-constant-default-value pass sets the default values for the
@@ -472,20 +492,6 @@
 // inserts created by that pass.
 Optimizer::PassToken CreateDeadInsertElimPass();
 
-// Creates a pass to consolidate uniform references.
-// For each entry point function in the module, first change all constant index
-// access chain loads into equivalent composite extracts. Then consolidate
-// identical uniform loads into one uniform load. Finally, consolidate
-// identical uniform extracts into one uniform extract. This may require
-// moving a load or extract to a point which dominates all uses.
-//
-// This pass requires a module to have structured control flow ie shader
-// capability. It also requires logical addressing ie Addresses capability
-// is not enabled. It also currently does not support any extensions.
-//
-// This pass currently only optimizes loads with a single index.
-Optimizer::PassToken CreateCommonUniformElimPass();
-
 // Create aggressive dead code elimination pass
 // This pass eliminates unused code from the module. In addition,
 // it detects and eliminates code which may have spurious uses but which do
@@ -692,10 +698,14 @@
 
 // Create a pass to instrument bindless descriptor checking
 // This pass instruments all bindless references to check that descriptor
-// array indices are inbounds. If the reference is invalid, a record is
-// written to the debug output buffer (if space allows) and a null value is
-// returned. This pass is designed to support bindless validation in the Vulkan
-// validation layers.
+// array indices are inbounds, and if the descriptor indexing extension is
+// enabled, that the descriptor has been initialized. If the reference is
+// invalid, a record is written to the debug output buffer (if space allows)
+// and a null value is returned. This pass is designed to support bindless
+// validation in the Vulkan validation layers.
+//
+// TODO(greg-lunarg): Add support for buffer references. Currently only does
+// checking for image references.
 //
 // Dead code elimination should be run after this pass as the original,
 // potentially invalid code is not removed and could cause undefined behavior,
@@ -711,10 +721,13 @@
 // The instrumentation will read and write buffers in debug
 // descriptor set |desc_set|. It will write |shader_id| in each output record
 // to identify the shader module which generated the record.
-//
-// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
-Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id);
+// |input_length_enable| controls instrumentation of runtime descriptor array
+// references, and |input_init_enable| controls instrumentation of descriptor
+// initialization checking, both of which require input buffer support.
+// |version| specifies the buffer record format.
+Optimizer::PassToken CreateInstBindlessCheckPass(
+    uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
+    bool input_init_enable = false, uint32_t version = 1);
 
 // Create a pass to upgrade to the VulkanKHR memory model.
 // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
@@ -726,6 +739,30 @@
 // where an instruction is moved into a more deeply nested construct.
 Optimizer::PassToken CreateCodeSinkingPass();
 
+// Create a pass to adds initializers for OpVariable calls that require them
+// in WebGPU. Currently this pass naively initializes variables that are
+// missing an initializer with a null value. In the future it may initialize
+// variables to the first value stored in them, if that is a constant.
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass();
+
+// Create a pass to fix incorrect storage classes.  In order to make code
+// generation simpler, DXC may generate code where the storage classes do not
+// match up correctly.  This pass will fix the errors that it can.
+Optimizer::PassToken CreateFixStorageClassPass();
+
+// Create a pass to legalize OpVectorShuffle operands going into WebGPU. WebGPU
+// forbids using 0xFFFFFFFF, which indicates an undefined result, so this pass
+// converts those literals to 0.
+Optimizer::PassToken CreateLegalizeVectorShufflePass();
+
+// Create a pass to decompose initialized variables into a seperate variable
+// declaration and an initial store.
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
+
+// Create a pass to attempt to split up invalid unreachable merge-blocks and
+// continue-targets to legalize for WebGPU.
+Optimizer::PassToken CreateSplitInvalidUnreachablePass();
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/android/build.sh b/kokoro/android/build.sh
index e31744f..c5a0a87 100644
--- a/kokoro/android/build.sh
+++ b/kokoro/android/build.sh
@@ -44,7 +44,7 @@
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-14 -DANDROID_ABI="armeabi-v7a with NEON" -DSPIRV_BUILD_COMPRESSION=ON -DSPIRV_SKIP_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_PATH -GNinja -DANDROID_NDK=$ANDROID_NDK ..
+cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -DCMAKE_BUILD_TYPE=Release -DANDROID_NATIVE_API_LEVEL=android-14 -DANDROID_ABI="armeabi-v7a with NEON" -DSPIRV_SKIP_TESTS=ON -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_PATH -GNinja -DANDROID_NDK=$ANDROID_NDK ..
 
 echo $(date): Build everything...
 ninja
diff --git a/kokoro/linux-clang-asan/build.sh b/kokoro/linux-clang-asan/build.sh
new file mode 100644
index 0000000..8f86e6e
--- /dev/null
+++ b/kokoro/linux-clang-asan/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Linux Build Script.
+
+# Fail on any error.
+set -e
+# Display commands being run.
+set -x
+
+SCRIPT_DIR=`dirname "$BASH_SOURCE"`
+source $SCRIPT_DIR/../scripts/linux/build.sh ASAN clang
diff --git a/kokoro/linux-clang-asan/continuous.cfg b/kokoro/linux-clang-asan/continuous.cfg
new file mode 100644
index 0000000..3a98fc7
--- /dev/null
+++ b/kokoro/linux-clang-asan/continuous.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Continuous build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-asan/presubmit.cfg b/kokoro/linux-clang-asan/presubmit.cfg
new file mode 100644
index 0000000..ceac44b
--- /dev/null
+++ b/kokoro/linux-clang-asan/presubmit.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Presubmit build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-debug/continuous.cfg b/kokoro/linux-clang-debug/continuous.cfg
index e92f059..3350f3b 100644
--- a/kokoro/linux-clang-debug/continuous.cfg
+++ b/kokoro/linux-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-clang-release/continuous.cfg b/kokoro/linux-clang-release/continuous.cfg
index 687434a..8b075c6 100644
--- a/kokoro/linux-clang-release/continuous.cfg
+++ b/kokoro/linux-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-debug/continuous.cfg b/kokoro/linux-gcc-debug/continuous.cfg
index 4f8418d..d9579d5 100644
--- a/kokoro/linux-gcc-debug/continuous.cfg
+++ b/kokoro/linux-gcc-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-release/continuous.cfg b/kokoro/linux-gcc-release/continuous.cfg
index 41a0024..ead07bf 100644
--- a/kokoro/linux-gcc-release/continuous.cfg
+++ b/kokoro/linux-gcc-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-debug/continuous.cfg b/kokoro/macos-clang-debug/continuous.cfg
index 84aaa5c..f5f274a 100644
--- a/kokoro/macos-clang-debug/continuous.cfg
+++ b/kokoro/macos-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-release/continuous.cfg b/kokoro/macos-clang-release/continuous.cfg
index a8e23a7..710185e 100644
--- a/kokoro/macos-clang-release/continuous.cfg
+++ b/kokoro/macos-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index d457539..f7b0fe1 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -31,8 +31,7 @@
 CMAKE_C_CXX_COMPILER=""
 if [ $COMPILER = "clang" ]
 then
-  sudo ln -s /usr/bin/clang-3.8 /usr/bin/clang
-  sudo ln -s /usr/bin/clang++-3.8 /usr/bin/clang++
+  PATH=/usr/lib/llvm-3.8/bin:$PATH
   CMAKE_C_CXX_COMPILER="-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
 fi
 
@@ -47,8 +46,8 @@
 ADDITIONAL_CMAKE_FLAGS=""
 if [ $CONFIG = "ASAN" ]
 then
-  ADDITIONAL_CMAKE_FLAGS="-DCMAKE_CXX_FLAGS=-fsanitize=address -DCMAKE_C_FLAGS=-fsanitize=address"
-  export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
+  ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address"
+  [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
 elif [ $CONFIG = "COVERAGE" ]
 then
   ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON"
@@ -72,13 +71,18 @@
 git clone --depth=1 https://github.com/google/googletest          external/googletest
 git clone --depth=1 https://github.com/google/effcee              external/effcee
 git clone --depth=1 https://github.com/google/re2                 external/re2
+git clone --depth=1 https://github.com/protocolbuffers/protobuf   external/protobuf
+pushd external/protobuf
+git fetch --all --tags --prune
+git checkout v3.7.1
+popd
 
 mkdir build && cd $SRC/build
 
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
+cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -GNinja -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DRE2_BUILD_TESTING=OFF -DSPIRV_BUILD_FUZZER=ON $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
 
 echo $(date): Build everything...
 ninja
@@ -98,3 +102,7 @@
 fi
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index a7f0453..5a3af43 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -35,13 +35,27 @@
 git clone --depth=1 https://github.com/google/googletest          external/googletest
 git clone --depth=1 https://github.com/google/effcee              external/effcee
 git clone --depth=1 https://github.com/google/re2                 external/re2
+git clone --depth=1 https://github.com/protocolbuffers/protobuf   external/protobuf
+pushd external/protobuf
+git fetch --all --tags --prune
+git checkout v3.7.1
+popd
 
 mkdir build && cd $SRC/build
 
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+# We need Python 3.  At the moment python3.7 is the newest Python on Kokoro.
+cmake \
+  -GNinja \
+  -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install \
+  -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3.7 \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+  -DSPIRV_BUILD_FUZZER=ON \
+  ..
 
 echo $(date): Build everything...
 ninja
@@ -51,3 +65,8 @@
 ctest -j4 --output-on-failure --timeout 300
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
+
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index 1985419..a4f2bf0 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -21,14 +21,19 @@
 set BUILD_TYPE=%1
 set VS_VERSION=%2
 
-:: Force usage of python 2.7 rather than 3.6
-set PATH=C:\python27;%PATH%
+:: Force usage of python 3.6
+set PATH=C:\python36;%PATH%
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
 git clone --depth=1 https://github.com/google/googletest          external/googletest
 git clone --depth=1 https://github.com/google/effcee              external/effcee
 git clone --depth=1 https://github.com/google/re2                 external/re2
+git clone --depth=1 https://github.com/protocolbuffers/protobuf   external/protobuf
+pushd external\protobuf
+git fetch --all --tags --prune
+git checkout v3.7.1
+popd
 
 :: #########################################
 :: set up msvc build env
@@ -58,13 +63,18 @@
   set BUILD_SHA=%KOKORO_GITHUB_COMMIT%
 )
 
-set CMAKE_FLAGS=-GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+set CMAKE_FLAGS=-DCMAKE_INSTALL_PREFIX=%KOKORO_ARTIFACTS_DIR%\install -GNinja -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
 
 :: Skip building tests for VS2013
 if %VS_VERSION% == 2013 (
   set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_SKIP_TESTS=ON
 )
 
+:: Skip building spirv-fuzz for VS2013; it relies on protobufs which VS2013 cannot handle.
+if %VS_VERSION% NEQ 2013 (
+  set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_BUILD_FUZZER=ON
+)
+
 cmake %CMAKE_FLAGS% ..
 
 if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
@@ -87,6 +97,13 @@
 )
 echo "Tests Completed %DATE% %TIME%"
 
+:: ################################################
+:: Install and package.
+:: ################################################
+ninja install
+cd %KOKORO_ARTIFACTS_DIR%
+zip -r install.zip install
+
 :: Clean up some directories.
 rm -rf %SRC%\build
 rm -rf %SRC%\external
diff --git a/kokoro/shaderc-smoketest/build.sh b/kokoro/shaderc-smoketest/build.sh
index 638ca8c..0856c9b 100644
--- a/kokoro/shaderc-smoketest/build.sh
+++ b/kokoro/shaderc-smoketest/build.sh
@@ -37,7 +37,7 @@
 
 # Get shaderc dependencies. Link the appropriate SPIRV-Tools.
 git clone https://github.com/google/googletest.git
-git clone https://github.com/google/glslang.git
+git clone https://github.com/KhronosGroup/glslang.git
 ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
 git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
 git clone https://github.com/google/re2
diff --git a/kokoro/windows-msvc-2017-debug/continuous.cfg b/kokoro/windows-msvc-2017-debug/continuous.cfg
index b842c30..25c5e11 100644
--- a/kokoro/windows-msvc-2017-debug/continuous.cfg
+++ b/kokoro/windows-msvc-2017-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/kokoro/windows-msvc-2017-release/continuous.cfg b/kokoro/windows-msvc-2017-release/continuous.cfg
index 7b8c2ff..a9ac6ec 100644
--- a/kokoro/windows-msvc-2017-release/continuous.cfg
+++ b/kokoro/windows-msvc-2017-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 03efa91..cb63ff0 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -101,7 +101,7 @@
   list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
 endmacro(spvtools_opencl_tables)
 
-macro(spvtools_vendor_tables VENDOR_TABLE)
+macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME)
   set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
   set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
   add_custom_command(OUTPUT ${INSTS_FILE}
@@ -110,9 +110,9 @@
       --vendor-insts-output=${INSTS_FILE}
     DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.")
-  add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE})
-  set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build")
-  list(APPEND EXTINST_CPP_DEPENDS spirv-tools-${VENDOR_TABLE})
+  add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE})
+  set_property(TARGET spv-tools-${SHORT_NAME} PROPERTY FOLDER "SPIRV-Tools build")
+  list(APPEND EXTINST_CPP_DEPENDS spv-tools-${SHORT_NAME})
 endmacro(spvtools_vendor_tables)
 
 macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
@@ -134,11 +134,11 @@
 spvtools_enum_string_mapping("unified1")
 spvtools_opencl_tables("unified1")
 spvtools_glsl_tables("unified1")
-spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
-spvtools_vendor_tables("spv-amd-shader-trinary-minmax")
-spvtools_vendor_tables("spv-amd-gcn-shader")
-spvtools_vendor_tables("spv-amd-shader-ballot")
-spvtools_vendor_tables("debuginfo")
+spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp")
+spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm")
+spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs")
+spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb")
+spvtools_vendor_tables("debuginfo" "debuginfo")
 spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
 
 spvtools_vimsyntax("unified1" "1.0")
@@ -196,9 +196,9 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/pch_source.cpp
   PROPERTIES OBJECT_DEPENDS "${PCH_DEPENDS}")
 
-add_subdirectory(comp)
 add_subdirectory(opt)
 add_subdirectory(reduce)
+add_subdirectory(fuzz)
 add_subdirectory(link)
 
 set(SPIRV_SOURCES
@@ -221,7 +221,6 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.h
   ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h
   ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
-  ${CMAKE_CURRENT_SOURCE_DIR}/id_descriptor.h
   ${CMAKE_CURRENT_SOURCE_DIR}/instruction.h
   ${CMAKE_CURRENT_SOURCE_DIR}/latest_version_glsl_std_450_header.h
   ${CMAKE_CURRENT_SOURCE_DIR}/latest_version_opencl_std_header.h
@@ -235,6 +234,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_constant.h
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_definition.h
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/spirv_fuzzer_options.h
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_optimizer_options.h
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_reducer_options.h
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.h
@@ -254,7 +254,6 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/extensions.cpp
-  ${CMAKE_CURRENT_SOURCE_DIR}/id_descriptor.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/libspirv.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
@@ -263,6 +262,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/print.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/software_version.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/spirv_fuzzer_options.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_optimizer_options.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_reducer_options.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.cpp
@@ -299,10 +299,12 @@
   ${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_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_scopes.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_small_type_uses.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/decoration.h
   ${CMAKE_CURRENT_SOURCE_DIR}/val/basic_block.cpp
@@ -361,6 +363,14 @@
 )
 add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables )
 
+if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
+  find_library(LIBRT rt)
+  if(LIBRT)
+    target_link_libraries(${SPIRV_TOOLS} ${LIBRT})
+    target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT})
+  endif()
+endif()
+
 if(ENABLE_SPIRV_TOOLS_INSTALL)
   install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared
     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 4d98e3d..79f18ee 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -154,10 +154,11 @@
     CASE(InBoundsAccessChain),
     CASE(PtrAccessChain),
     CASE(InBoundsPtrAccessChain),
+    CASE(CooperativeMatrixLengthNV)
 };
 
-// The 59 is determined by counting the opcodes listed in the spec.
-static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 60 is determined by counting the opcodes listed in the spec.
+static_assert(60 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
               "OpSpecConstantOp opcode table is incomplete");
 #undef CASE
 // clang-format on
diff --git a/source/comp/CMakeLists.txt b/source/comp/CMakeLists.txt
deleted file mode 100644
index f65f9f6..0000000
--- a/source/comp/CMakeLists.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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.
-
-if(SPIRV_BUILD_COMPRESSION)
-  add_library(SPIRV-Tools-comp
-      bit_stream.cpp
-      bit_stream.h
-      huffman_codec.h
-      markv_codec.cpp
-      markv_codec.h
-      markv.cpp
-      markv.h
-      markv_decoder.cpp
-      markv_decoder.h
-      markv_encoder.cpp
-      markv_encoder.h
-      markv_logger.h
-      move_to_front.h
-      move_to_front.cpp)
-
-  spvtools_default_compile_options(SPIRV-Tools-comp)
-  target_include_directories(SPIRV-Tools-comp
-    PUBLIC ${spirv-tools_SOURCE_DIR}/include
-    PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
-    PRIVATE ${spirv-tools_BINARY_DIR}
-  )
-
-  target_link_libraries(SPIRV-Tools-comp
-    PUBLIC ${SPIRV_TOOLS})
-
-  set_property(TARGET SPIRV-Tools-comp PROPERTY FOLDER "SPIRV-Tools libraries")
-  spvtools_check_symbol_exports(SPIRV-Tools-comp)
-
-  if(ENABLE_SPIRV_TOOLS_INSTALL)
-    install(TARGETS SPIRV-Tools-comp
-      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
-  endif(ENABLE_SPIRV_TOOLS_INSTALL)
-
-endif(SPIRV_BUILD_COMPRESSION)
diff --git a/source/comp/bit_stream.cpp b/source/comp/bit_stream.cpp
deleted file mode 100644
index a5769e0..0000000
--- a/source/comp/bit_stream.cpp
+++ /dev/null
@@ -1,348 +0,0 @@
-// 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 <algorithm>
-#include <cassert>
-#include <cstring>
-#include <sstream>
-#include <type_traits>
-
-#include "source/comp/bit_stream.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-// Returns if the system is little-endian. Unfortunately only works during
-// runtime.
-bool IsLittleEndian() {
-  // This constant value allows the detection of the host machine's endianness.
-  // Accessing it as an array of bytes is valid due to C++11 section 3.10
-  // paragraph 10.
-  static const uint16_t kFF00 = 0xff00;
-  return reinterpret_cast<const unsigned char*>(&kFF00)[0] == 0;
-}
-
-// Copies bytes from the given buffer to a uint64_t buffer.
-// Motivation: casting uint64_t* to uint8_t* is ok. Casting in the other
-// direction is only advisable if uint8_t* is aligned to 64-bit word boundary.
-std::vector<uint64_t> ToBuffer64(const void* buffer, size_t num_bytes) {
-  std::vector<uint64_t> out;
-  out.resize((num_bytes + 7) / 8, 0);
-  memcpy(out.data(), buffer, num_bytes);
-  return out;
-}
-
-// Copies uint8_t buffer to a uint64_t buffer.
-std::vector<uint64_t> ToBuffer64(const std::vector<uint8_t>& in) {
-  return ToBuffer64(in.data(), in.size());
-}
-
-// Returns uint64_t containing the same bits as |val|.
-// Type size must be less than 8 bytes.
-template <typename T>
-uint64_t ToU64(T val) {
-  static_assert(sizeof(T) <= 8, "Type size too big");
-  uint64_t val64 = 0;
-  std::memcpy(&val64, &val, sizeof(T));
-  return val64;
-}
-
-// Returns value of type T containing the same bits as |val64|.
-// Type size must be less than 8 bytes. Upper (unused) bits of |val64| must be
-// zero (irrelevant, but is checked with assertion).
-template <typename T>
-T FromU64(uint64_t val64) {
-  assert(sizeof(T) == 8 || (val64 >> (sizeof(T) * 8)) == 0);
-  static_assert(sizeof(T) <= 8, "Type size too big");
-  T val = 0;
-  std::memcpy(&val, &val64, sizeof(T));
-  return val;
-}
-
-// Writes bits from |val| to |writer| in chunks of size |chunk_length|.
-// Signal bit is used to signal if the reader should expect another chunk:
-// 0 - no more chunks to follow
-// 1 - more chunks to follow
-// If number of written bits reaches |max_payload| last chunk is truncated.
-void WriteVariableWidthInternal(BitWriterInterface* writer, uint64_t val,
-                                size_t chunk_length, size_t max_payload) {
-  assert(chunk_length > 0);
-  assert(chunk_length < max_payload);
-  assert(max_payload == 64 || (val >> max_payload) == 0);
-
-  if (val == 0) {
-    // Split in two writes for more readable logging.
-    writer->WriteBits(0, chunk_length);
-    writer->WriteBits(0, 1);
-    return;
-  }
-
-  size_t payload_written = 0;
-
-  while (val) {
-    if (payload_written + chunk_length >= max_payload) {
-      // This has to be the last chunk.
-      // There is no need for the signal bit and the chunk can be truncated.
-      const size_t left_to_write = max_payload - payload_written;
-      assert((val >> left_to_write) == 0);
-      writer->WriteBits(val, left_to_write);
-      break;
-    }
-
-    writer->WriteBits(val, chunk_length);
-    payload_written += chunk_length;
-    val = val >> chunk_length;
-
-    // Write a single bit to signal if there is more to come.
-    writer->WriteBits(val ? 1 : 0, 1);
-  }
-}
-
-// Reads data written with WriteVariableWidthInternal. |chunk_length| and
-// |max_payload| should be identical to those used to write the data.
-// Returns false if the stream ends prematurely.
-bool ReadVariableWidthInternal(BitReaderInterface* reader, uint64_t* val,
-                               size_t chunk_length, size_t max_payload) {
-  assert(chunk_length > 0);
-  assert(chunk_length <= max_payload);
-  size_t payload_read = 0;
-
-  while (payload_read + chunk_length < max_payload) {
-    uint64_t bits = 0;
-    if (reader->ReadBits(&bits, chunk_length) != chunk_length) return false;
-
-    *val |= bits << payload_read;
-    payload_read += chunk_length;
-
-    uint64_t more_to_come = 0;
-    if (reader->ReadBits(&more_to_come, 1) != 1) return false;
-
-    if (!more_to_come) {
-      return true;
-    }
-  }
-
-  // Need to read the last chunk which may be truncated. No signal bit follows.
-  uint64_t bits = 0;
-  const size_t left_to_read = max_payload - payload_read;
-  if (reader->ReadBits(&bits, left_to_read) != left_to_read) return false;
-
-  *val |= bits << payload_read;
-  return true;
-}
-
-// Calls WriteVariableWidthInternal with the right max_payload argument.
-template <typename T>
-void WriteVariableWidthUnsigned(BitWriterInterface* writer, T val,
-                                size_t chunk_length) {
-  static_assert(std::is_unsigned<T>::value, "Type must be unsigned");
-  static_assert(std::is_integral<T>::value, "Type must be integral");
-  WriteVariableWidthInternal(writer, val, chunk_length, sizeof(T) * 8);
-}
-
-// Calls ReadVariableWidthInternal with the right max_payload argument.
-template <typename T>
-bool ReadVariableWidthUnsigned(BitReaderInterface* reader, T* val,
-                               size_t chunk_length) {
-  static_assert(std::is_unsigned<T>::value, "Type must be unsigned");
-  static_assert(std::is_integral<T>::value, "Type must be integral");
-  uint64_t val64 = 0;
-  if (!ReadVariableWidthInternal(reader, &val64, chunk_length, sizeof(T) * 8))
-    return false;
-  *val = static_cast<T>(val64);
-  assert(*val == val64);
-  return true;
-}
-
-// Encodes signed |val| to an unsigned value and calls
-// WriteVariableWidthInternal with the right max_payload argument.
-template <typename T>
-void WriteVariableWidthSigned(BitWriterInterface* writer, T val,
-                              size_t chunk_length, size_t zigzag_exponent) {
-  static_assert(std::is_signed<T>::value, "Type must be signed");
-  static_assert(std::is_integral<T>::value, "Type must be integral");
-  WriteVariableWidthInternal(writer, EncodeZigZag(val, zigzag_exponent),
-                             chunk_length, sizeof(T) * 8);
-}
-
-// Calls ReadVariableWidthInternal with the right max_payload argument
-// and decodes the value.
-template <typename T>
-bool ReadVariableWidthSigned(BitReaderInterface* reader, T* val,
-                             size_t chunk_length, size_t zigzag_exponent) {
-  static_assert(std::is_signed<T>::value, "Type must be signed");
-  static_assert(std::is_integral<T>::value, "Type must be integral");
-  uint64_t encoded = 0;
-  if (!ReadVariableWidthInternal(reader, &encoded, chunk_length, sizeof(T) * 8))
-    return false;
-
-  const int64_t decoded = DecodeZigZag(encoded, zigzag_exponent);
-
-  *val = static_cast<T>(decoded);
-  assert(*val == decoded);
-  return true;
-}
-
-}  // namespace
-
-void BitWriterInterface::WriteVariableWidthU64(uint64_t val,
-                                               size_t chunk_length) {
-  WriteVariableWidthUnsigned(this, val, chunk_length);
-}
-
-void BitWriterInterface::WriteVariableWidthU32(uint32_t val,
-                                               size_t chunk_length) {
-  WriteVariableWidthUnsigned(this, val, chunk_length);
-}
-
-void BitWriterInterface::WriteVariableWidthU16(uint16_t val,
-                                               size_t chunk_length) {
-  WriteVariableWidthUnsigned(this, val, chunk_length);
-}
-
-void BitWriterInterface::WriteVariableWidthS64(int64_t val, size_t chunk_length,
-                                               size_t zigzag_exponent) {
-  WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
-}
-
-BitWriterWord64::BitWriterWord64(size_t reserve_bits) : end_(0) {
-  buffer_.reserve(NumBitsToNumWords<64>(reserve_bits));
-}
-
-void BitWriterWord64::WriteBits(uint64_t bits, size_t num_bits) {
-  // Check that |bits| and |num_bits| are valid and consistent.
-  assert(num_bits <= 64);
-  const bool is_little_endian = IsLittleEndian();
-  assert(is_little_endian && "Big-endian architecture support not implemented");
-  if (!is_little_endian) return;
-
-  if (num_bits == 0) return;
-
-  bits = GetLowerBits(bits, num_bits);
-
-  EmitSequence(bits, num_bits);
-
-  // Offset from the start of the current word.
-  const size_t offset = end_ % 64;
-
-  if (offset == 0) {
-    // If no offset, simply add |bits| as a new word to the buffer_.
-    buffer_.push_back(bits);
-  } else {
-    // Shift bits and add them to the current word after offset.
-    const uint64_t first_word = bits << offset;
-    buffer_.back() |= first_word;
-
-    // If we don't overflow to the next word, there is nothing more to do.
-
-    if (offset + num_bits > 64) {
-      // We overflow to the next word.
-      const uint64_t second_word = bits >> (64 - offset);
-      // Add remaining bits as a new word to buffer_.
-      buffer_.push_back(second_word);
-    }
-  }
-
-  // Move end_ into position for next write.
-  end_ += num_bits;
-  assert(buffer_.size() * 64 >= end_);
-}
-
-bool BitReaderInterface::ReadVariableWidthU64(uint64_t* val,
-                                              size_t chunk_length) {
-  return ReadVariableWidthUnsigned(this, val, chunk_length);
-}
-
-bool BitReaderInterface::ReadVariableWidthU32(uint32_t* val,
-                                              size_t chunk_length) {
-  return ReadVariableWidthUnsigned(this, val, chunk_length);
-}
-
-bool BitReaderInterface::ReadVariableWidthU16(uint16_t* val,
-                                              size_t chunk_length) {
-  return ReadVariableWidthUnsigned(this, val, chunk_length);
-}
-
-bool BitReaderInterface::ReadVariableWidthS64(int64_t* val, size_t chunk_length,
-                                              size_t zigzag_exponent) {
-  return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent);
-}
-
-BitReaderWord64::BitReaderWord64(std::vector<uint64_t>&& buffer)
-    : buffer_(std::move(buffer)), pos_(0) {}
-
-BitReaderWord64::BitReaderWord64(const std::vector<uint8_t>& buffer)
-    : buffer_(ToBuffer64(buffer)), pos_(0) {}
-
-BitReaderWord64::BitReaderWord64(const void* buffer, size_t num_bytes)
-    : buffer_(ToBuffer64(buffer, num_bytes)), pos_(0) {}
-
-size_t BitReaderWord64::ReadBits(uint64_t* bits, size_t num_bits) {
-  assert(num_bits <= 64);
-  const bool is_little_endian = IsLittleEndian();
-  assert(is_little_endian && "Big-endian architecture support not implemented");
-  if (!is_little_endian) return 0;
-
-  if (ReachedEnd()) return 0;
-
-  // Index of the current word.
-  const size_t index = pos_ / 64;
-
-  // Bit position in the current word where we start reading.
-  const size_t offset = pos_ % 64;
-
-  // Read all bits from the current word (it might be too much, but
-  // excessive bits will be removed later).
-  *bits = buffer_[index] >> offset;
-
-  const size_t num_read_from_first_word = std::min(64 - offset, num_bits);
-  pos_ += num_read_from_first_word;
-
-  if (pos_ >= buffer_.size() * 64) {
-    // Reached end of buffer_.
-    EmitSequence(*bits, num_read_from_first_word);
-    return num_read_from_first_word;
-  }
-
-  if (offset + num_bits > 64) {
-    // Requested |num_bits| overflows to next word.
-    // Write all bits from the beginning of next word to *bits after offset.
-    *bits |= buffer_[index + 1] << (64 - offset);
-    pos_ += offset + num_bits - 64;
-  }
-
-  // We likely have written more bits than requested. Clear excessive bits.
-  *bits = GetLowerBits(*bits, num_bits);
-  EmitSequence(*bits, num_bits);
-  return num_bits;
-}
-
-bool BitReaderWord64::ReachedEnd() const { return pos_ >= buffer_.size() * 64; }
-
-bool BitReaderWord64::OnlyZeroesLeft() const {
-  if (ReachedEnd()) return true;
-
-  const size_t index = pos_ / 64;
-  if (index < buffer_.size() - 1) return false;
-
-  assert(index == buffer_.size() - 1);
-
-  const size_t offset = pos_ % 64;
-  const uint64_t remaining_bits = buffer_[index] >> offset;
-  return !remaining_bits;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/bit_stream.h b/source/comp/bit_stream.h
deleted file mode 100644
index 5f82344..0000000
--- a/source/comp/bit_stream.h
+++ /dev/null
@@ -1,280 +0,0 @@
-// 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.
-
-// Contains utils for reading, writing and debug printing bit streams.
-
-#ifndef SOURCE_COMP_BIT_STREAM_H_
-#define SOURCE_COMP_BIT_STREAM_H_
-
-#include <algorithm>
-#include <bitset>
-#include <cassert>
-#include <cstdint>
-#include <cstring>
-#include <functional>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-namespace spvtools {
-namespace comp {
-
-// Terminology:
-// Bits - usually used for a uint64 word, first bit is the lowest.
-// Stream - std::string of '0' and '1', read left-to-right,
-//          i.e. first bit is at the front and not at the end as in
-//          std::bitset::to_string().
-// Bitset - std::bitset corresponding to uint64 bits and to reverse(stream).
-
-// Converts number of bits to a respective number of chunks of size N.
-// For example NumBitsToNumWords<8> returns how many bytes are needed to store
-// |num_bits|.
-template <size_t N>
-inline size_t NumBitsToNumWords(size_t num_bits) {
-  return (num_bits + (N - 1)) / N;
-}
-
-// Returns value of the same type as |in|, where all but the first |num_bits|
-// are set to zero.
-template <typename T>
-inline T GetLowerBits(T in, size_t num_bits) {
-  return sizeof(T) * 8 == num_bits ? in : in & T((T(1) << num_bits) - T(1));
-}
-
-// Encodes signed integer as unsigned. This is a generalized version of
-// EncodeZigZag, designed to favor small positive numbers.
-// Values are transformed in blocks of 2^|block_exponent|.
-// If |block_exponent| is zero, then this degenerates into normal EncodeZigZag.
-// Example when |block_exponent| is 1 (return value is the index):
-// 0, 1, -1, -2, 2, 3, -3, -4, 4, 5, -5, -6, 6, 7, -7, -8
-// Example when |block_exponent| is 2:
-// 0, 1, 2, 3, -1, -2, -3, -4, 4, 5, 6, 7, -5, -6, -7, -8
-inline uint64_t EncodeZigZag(int64_t val, size_t block_exponent) {
-  assert(block_exponent < 64);
-  const uint64_t uval = static_cast<uint64_t>(val >= 0 ? val : -val - 1);
-  const uint64_t block_num =
-      ((uval >> block_exponent) << 1) + (val >= 0 ? 0 : 1);
-  const uint64_t pos = GetLowerBits(uval, block_exponent);
-  return (block_num << block_exponent) + pos;
-}
-
-// Decodes signed integer encoded with EncodeZigZag. |block_exponent| must be
-// the same.
-inline int64_t DecodeZigZag(uint64_t val, size_t block_exponent) {
-  assert(block_exponent < 64);
-  const uint64_t block_num = val >> block_exponent;
-  const uint64_t pos = GetLowerBits(val, block_exponent);
-  if (block_num & 1) {
-    // Negative.
-    return -1LL - ((block_num >> 1) << block_exponent) - pos;
-  } else {
-    // Positive.
-    return ((block_num >> 1) << block_exponent) + pos;
-  }
-}
-
-// Converts first |num_bits| stored in uint64 to a left-to-right stream of bits.
-inline std::string BitsToStream(uint64_t bits, size_t num_bits = 64) {
-  std::bitset<64> bitset(bits);
-  std::string str = bitset.to_string().substr(64 - num_bits);
-  std::reverse(str.begin(), str.end());
-  return str;
-}
-
-// Base class for writing sequences of bits.
-class BitWriterInterface {
- public:
-  BitWriterInterface() = default;
-  virtual ~BitWriterInterface() = default;
-
-  // Writes lower |num_bits| in |bits| to the stream.
-  // |num_bits| must be no greater than 64.
-  virtual void WriteBits(uint64_t bits, size_t num_bits) = 0;
-
-  // Writes bits from value of type |T| to the stream. No encoding is done.
-  // Always writes 8 * sizeof(T) bits.
-  template <typename T>
-  void WriteUnencoded(T val) {
-    static_assert(sizeof(T) <= 64, "Type size too large");
-    uint64_t bits = 0;
-    memcpy(&bits, &val, sizeof(T));
-    WriteBits(bits, sizeof(T) * 8);
-  }
-
-  // Writes |val| in chunks of size |chunk_length| followed by a signal bit:
-  // 0 - no more chunks to follow
-  // 1 - more chunks to follow
-  // for example 255 is encoded into 1111 1 1111 0 for chunk length 4.
-  // The last chunk can be truncated and signal bit omitted, if the entire
-  // payload (for example 16 bit for uint16_t has already been written).
-  void WriteVariableWidthU64(uint64_t val, size_t chunk_length);
-  void WriteVariableWidthU32(uint32_t val, size_t chunk_length);
-  void WriteVariableWidthU16(uint16_t val, size_t chunk_length);
-  void WriteVariableWidthS64(int64_t val, size_t chunk_length,
-                             size_t zigzag_exponent);
-
-  // Returns number of bits written.
-  virtual size_t GetNumBits() const = 0;
-
-  // Provides direct access to the buffer data if implemented.
-  virtual const uint8_t* GetData() const { return nullptr; }
-
-  // Returns buffer size in bytes.
-  size_t GetDataSizeBytes() const { return NumBitsToNumWords<8>(GetNumBits()); }
-
-  // Generates and returns byte array containing written bits.
-  virtual std::vector<uint8_t> GetDataCopy() const = 0;
-
-  BitWriterInterface(const BitWriterInterface&) = delete;
-  BitWriterInterface& operator=(const BitWriterInterface&) = delete;
-};
-
-// This class is an implementation of BitWriterInterface, using
-// std::vector<uint64_t> to store written bits.
-class BitWriterWord64 : public BitWriterInterface {
- public:
-  explicit BitWriterWord64(size_t reserve_bits = 64);
-
-  void WriteBits(uint64_t bits, size_t num_bits) override;
-
-  size_t GetNumBits() const override { return end_; }
-
-  const uint8_t* GetData() const override {
-    return reinterpret_cast<const uint8_t*>(buffer_.data());
-  }
-
-  std::vector<uint8_t> GetDataCopy() const override {
-    return std::vector<uint8_t>(GetData(), GetData() + GetDataSizeBytes());
-  }
-
-  // Sets callback to emit bit sequences after every write.
-  void SetCallback(std::function<void(const std::string&)> callback) {
-    callback_ = callback;
-  }
-
- protected:
-  // Sends string generated from arguments to callback_ if defined.
-  void EmitSequence(uint64_t bits, size_t num_bits) const {
-    if (callback_) callback_(BitsToStream(bits, num_bits));
-  }
-
- private:
-  std::vector<uint64_t> buffer_;
-  // Total number of bits written so far. Named 'end' as analogy to std::end().
-  size_t end_;
-
-  // If not null, the writer will use the callback to emit the written bit
-  // sequence as a string of '0' and '1'.
-  std::function<void(const std::string&)> callback_;
-};
-
-// Base class for reading sequences of bits.
-class BitReaderInterface {
- public:
-  BitReaderInterface() {}
-  virtual ~BitReaderInterface() {}
-
-  // Reads |num_bits| from the stream, stores them in |bits|.
-  // Returns number of read bits. |num_bits| must be no greater than 64.
-  virtual size_t ReadBits(uint64_t* bits, size_t num_bits) = 0;
-
-  // Reads 8 * sizeof(T) bits and stores them in |val|.
-  template <typename T>
-  bool ReadUnencoded(T* val) {
-    static_assert(sizeof(T) <= 64, "Type size too large");
-    uint64_t bits = 0;
-    const size_t num_read = ReadBits(&bits, sizeof(T) * 8);
-    if (num_read != sizeof(T) * 8) return false;
-    memcpy(val, &bits, sizeof(T));
-    return true;
-  }
-
-  // Returns number of bits already read.
-  virtual size_t GetNumReadBits() const = 0;
-
-  // These two functions define 'hard' and 'soft' EOF.
-  //
-  // Returns true if the end of the buffer was reached.
-  virtual bool ReachedEnd() const = 0;
-  // Returns true if we reached the end of the buffer or are nearing it and only
-  // zero bits are left to read. Implementations of this function are allowed to
-  // commit a "false negative" error if the end of the buffer was not reached,
-  // i.e. it can return false even if indeed only zeroes are left.
-  // It is assumed that the consumer expects that
-  // the buffer stream ends with padding zeroes, and would accept this as a
-  // 'soft' EOF. Implementations of this class do not necessarily need to
-  // implement this, default behavior can simply delegate to ReachedEnd().
-  virtual bool OnlyZeroesLeft() const { return ReachedEnd(); }
-
-  // Reads value encoded with WriteVariableWidthXXX (see BitWriterInterface).
-  // Reader and writer must use the same |chunk_length| and variable type.
-  // Returns true on success, false if the bit stream ends prematurely.
-  bool ReadVariableWidthU64(uint64_t* val, size_t chunk_length);
-  bool ReadVariableWidthU32(uint32_t* val, size_t chunk_length);
-  bool ReadVariableWidthU16(uint16_t* val, size_t chunk_length);
-  bool ReadVariableWidthS64(int64_t* val, size_t chunk_length,
-                            size_t zigzag_exponent);
-
-  BitReaderInterface(const BitReaderInterface&) = delete;
-  BitReaderInterface& operator=(const BitReaderInterface&) = delete;
-};
-
-// This class is an implementation of BitReaderInterface which accepts both
-// uint8_t and uint64_t buffers as input. uint64_t buffers are consumed and
-// owned. uint8_t buffers are copied.
-class BitReaderWord64 : public BitReaderInterface {
- public:
-  // Consumes and owns the buffer.
-  explicit BitReaderWord64(std::vector<uint64_t>&& buffer);
-
-  // Copies the buffer and casts it to uint64.
-  // Consuming the original buffer and casting it to uint64 is difficult,
-  // as it would potentially cause data misalignment and poor performance.
-  explicit BitReaderWord64(const std::vector<uint8_t>& buffer);
-  BitReaderWord64(const void* buffer, size_t num_bytes);
-
-  size_t ReadBits(uint64_t* bits, size_t num_bits) override;
-
-  size_t GetNumReadBits() const override { return pos_; }
-
-  bool ReachedEnd() const override;
-  bool OnlyZeroesLeft() const override;
-
-  BitReaderWord64() = delete;
-
-  // Sets callback to emit bit sequences after every read.
-  void SetCallback(std::function<void(const std::string&)> callback) {
-    callback_ = callback;
-  }
-
- protected:
-  // Sends string generated from arguments to callback_ if defined.
-  void EmitSequence(uint64_t bits, size_t num_bits) const {
-    if (callback_) callback_(BitsToStream(bits, num_bits));
-  }
-
- private:
-  const std::vector<uint64_t> buffer_;
-  size_t pos_;
-
-  // If not null, the reader will use the callback to emit the read bit
-  // sequence as a string of '0' and '1'.
-  std::function<void(const std::string&)> callback_;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_BIT_STREAM_H_
diff --git a/source/comp/huffman_codec.h b/source/comp/huffman_codec.h
deleted file mode 100644
index 1660216..0000000
--- a/source/comp/huffman_codec.h
+++ /dev/null
@@ -1,389 +0,0 @@
-// 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.
-
-// Contains utils for reading, writing and debug printing bit streams.
-
-#ifndef SOURCE_COMP_HUFFMAN_CODEC_H_
-#define SOURCE_COMP_HUFFMAN_CODEC_H_
-
-#include <algorithm>
-#include <cassert>
-#include <functional>
-#include <iomanip>
-#include <map>
-#include <memory>
-#include <ostream>
-#include <queue>
-#include <sstream>
-#include <stack>
-#include <string>
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-namespace spvtools {
-namespace comp {
-
-// Used to generate and apply a Huffman coding scheme.
-// |Val| is the type of variable being encoded (for example a string or a
-// literal).
-template <class Val>
-class HuffmanCodec {
- public:
-  // Huffman tree node.
-  struct Node {
-    Node() {}
-
-    // Creates Node from serialization leaving weight and id undefined.
-    Node(const Val& in_value, uint32_t in_left, uint32_t in_right)
-        : value(in_value), left(in_left), right(in_right) {}
-
-    Val value = Val();
-    uint32_t weight = 0;
-    // Ids are issued sequentially starting from 1. Ids are used as an ordering
-    // tie-breaker, to make sure that the ordering (and resulting coding scheme)
-    // are consistent accross multiple platforms.
-    uint32_t id = 0;
-    // Handles of children.
-    uint32_t left = 0;
-    uint32_t right = 0;
-  };
-
-  // Creates Huffman codec from a histogramm.
-  // Histogramm counts must not be zero.
-  explicit HuffmanCodec(const std::map<Val, uint32_t>& hist) {
-    if (hist.empty()) return;
-
-    // Heuristic estimate.
-    nodes_.reserve(3 * hist.size());
-
-    // Create NIL.
-    CreateNode();
-
-    // The queue is sorted in ascending order by weight (or by node id if
-    // weights are equal).
-    std::vector<uint32_t> queue_vector;
-    queue_vector.reserve(hist.size());
-    std::priority_queue<uint32_t, std::vector<uint32_t>,
-                        std::function<bool(uint32_t, uint32_t)>>
-        queue(std::bind(&HuffmanCodec::LeftIsBigger, this,
-                        std::placeholders::_1, std::placeholders::_2),
-              std::move(queue_vector));
-
-    // Put all leaves in the queue.
-    for (const auto& pair : hist) {
-      const uint32_t node = CreateNode();
-      MutableValueOf(node) = pair.first;
-      MutableWeightOf(node) = pair.second;
-      assert(WeightOf(node));
-      queue.push(node);
-    }
-
-    // Form the tree by combining two subtrees with the least weight,
-    // and pushing the root of the new tree in the queue.
-    while (true) {
-      // We push a node at the end of each iteration, so the queue is never
-      // supposed to be empty at this point, unless there are no leaves, but
-      // that case was already handled.
-      assert(!queue.empty());
-      const uint32_t right = queue.top();
-      queue.pop();
-
-      // If the queue is empty at this point, then the last node is
-      // the root of the complete Huffman tree.
-      if (queue.empty()) {
-        root_ = right;
-        break;
-      }
-
-      const uint32_t left = queue.top();
-      queue.pop();
-
-      // Combine left and right into a new tree and push it into the queue.
-      const uint32_t parent = CreateNode();
-      MutableWeightOf(parent) = WeightOf(right) + WeightOf(left);
-      MutableLeftOf(parent) = left;
-      MutableRightOf(parent) = right;
-      queue.push(parent);
-    }
-
-    // Traverse the tree and form encoding table.
-    CreateEncodingTable();
-  }
-
-  // Creates Huffman codec from saved tree structure.
-  // |nodes| is the list of nodes of the tree, nodes[0] being NIL.
-  // |root_handle| is the index of the root node.
-  HuffmanCodec(uint32_t root_handle, std::vector<Node>&& nodes) {
-    nodes_ = std::move(nodes);
-    assert(!nodes_.empty());
-    assert(root_handle > 0 && root_handle < nodes_.size());
-    assert(!LeftOf(0) && !RightOf(0));
-
-    root_ = root_handle;
-
-    // Traverse the tree and form encoding table.
-    CreateEncodingTable();
-  }
-
-  // Serializes the codec in the following text format:
-  // (<root_handle>, {
-  //   {0, 0, 0},
-  //   {val1, left1, right1},
-  //   {val2, left2, right2},
-  //   ...
-  // })
-  std::string SerializeToText(int indent_num_whitespaces) const {
-    const bool value_is_text = std::is_same<Val, std::string>::value;
-
-    const std::string indent1 = std::string(indent_num_whitespaces, ' ');
-    const std::string indent2 = std::string(indent_num_whitespaces + 2, ' ');
-
-    std::stringstream code;
-    code << "(" << root_ << ", {\n";
-
-    for (const Node& node : nodes_) {
-      code << indent2 << "{";
-      if (value_is_text) code << "\"";
-      code << node.value;
-      if (value_is_text) code << "\"";
-      code << ", " << node.left << ", " << node.right << "},\n";
-    }
-
-    code << indent1 << "})";
-
-    return code.str();
-  }
-
-  // Prints the Huffman tree in the following format:
-  // w------w------'x'
-  //        w------'y'
-  // Where w stands for the weight of the node.
-  // Right tree branches appear above left branches. Taking the right path
-  // adds 1 to the code, taking the left adds 0.
-  void PrintTree(std::ostream& out) const { PrintTreeInternal(out, root_, 0); }
-
-  // Traverses the tree and prints the Huffman table: value, code
-  // and optionally node weight for every leaf.
-  void PrintTable(std::ostream& out, bool print_weights = true) {
-    std::queue<std::pair<uint32_t, std::string>> queue;
-    queue.emplace(root_, "");
-
-    while (!queue.empty()) {
-      const uint32_t node = queue.front().first;
-      const std::string code = queue.front().second;
-      queue.pop();
-      if (!RightOf(node) && !LeftOf(node)) {
-        out << ValueOf(node);
-        if (print_weights) out << " " << WeightOf(node);
-        out << " " << code << std::endl;
-      } else {
-        if (LeftOf(node)) queue.emplace(LeftOf(node), code + "0");
-
-        if (RightOf(node)) queue.emplace(RightOf(node), code + "1");
-      }
-    }
-  }
-
-  // Returns the Huffman table. The table was built at at construction time,
-  // this function just returns a const reference.
-  const std::unordered_map<Val, std::pair<uint64_t, size_t>>& GetEncodingTable()
-      const {
-    return encoding_table_;
-  }
-
-  // Encodes |val| and stores its Huffman code in the lower |num_bits| of
-  // |bits|. Returns false of |val| is not in the Huffman table.
-  bool Encode(const Val& val, uint64_t* bits, size_t* num_bits) const {
-    auto it = encoding_table_.find(val);
-    if (it == encoding_table_.end()) return false;
-    *bits = it->second.first;
-    *num_bits = it->second.second;
-    return true;
-  }
-
-  // Reads bits one-by-one using callback |read_bit| until a match is found.
-  // Matching value is stored in |val|. Returns false if |read_bit| terminates
-  // before a code was mathced.
-  // |read_bit| has type bool func(bool* bit). When called, the next bit is
-  // stored in |bit|. |read_bit| returns false if the stream terminates
-  // prematurely.
-  bool DecodeFromStream(const std::function<bool(bool*)>& read_bit,
-                        Val* val) const {
-    uint32_t node = root_;
-    while (true) {
-      assert(node);
-
-      if (!RightOf(node) && !LeftOf(node)) {
-        *val = ValueOf(node);
-        return true;
-      }
-
-      bool go_right;
-      if (!read_bit(&go_right)) return false;
-
-      if (go_right)
-        node = RightOf(node);
-      else
-        node = LeftOf(node);
-    }
-
-    assert(0);
-    return false;
-  }
-
- private:
-  // Returns value of the node referenced by |handle|.
-  Val ValueOf(uint32_t node) const { return nodes_.at(node).value; }
-
-  // Returns left child of |node|.
-  uint32_t LeftOf(uint32_t node) const { return nodes_.at(node).left; }
-
-  // Returns right child of |node|.
-  uint32_t RightOf(uint32_t node) const { return nodes_.at(node).right; }
-
-  // Returns weight of |node|.
-  uint32_t WeightOf(uint32_t node) const { return nodes_.at(node).weight; }
-
-  // Returns id of |node|.
-  uint32_t IdOf(uint32_t node) const { return nodes_.at(node).id; }
-
-  // Returns mutable reference to value of |node|.
-  Val& MutableValueOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).value;
-  }
-
-  // Returns mutable reference to handle of left child of |node|.
-  uint32_t& MutableLeftOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).left;
-  }
-
-  // Returns mutable reference to handle of right child of |node|.
-  uint32_t& MutableRightOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).right;
-  }
-
-  // Returns mutable reference to weight of |node|.
-  uint32_t& MutableWeightOf(uint32_t node) { return nodes_.at(node).weight; }
-
-  // Returns mutable reference to id of |node|.
-  uint32_t& MutableIdOf(uint32_t node) { return nodes_.at(node).id; }
-
-  // Returns true if |left| has bigger weight than |right|. Node ids are
-  // used as tie-breaker.
-  bool LeftIsBigger(uint32_t left, uint32_t right) const {
-    if (WeightOf(left) == WeightOf(right)) {
-      assert(IdOf(left) != IdOf(right));
-      return IdOf(left) > IdOf(right);
-    }
-    return WeightOf(left) > WeightOf(right);
-  }
-
-  // Prints subtree (helper function used by PrintTree).
-  void PrintTreeInternal(std::ostream& out, uint32_t node, size_t depth) const {
-    if (!node) return;
-
-    const size_t kTextFieldWidth = 7;
-
-    if (!RightOf(node) && !LeftOf(node)) {
-      out << ValueOf(node) << std::endl;
-    } else {
-      if (RightOf(node)) {
-        std::stringstream label;
-        label << std::setfill('-') << std::left << std::setw(kTextFieldWidth)
-              << WeightOf(RightOf(node));
-        out << label.str();
-        PrintTreeInternal(out, RightOf(node), depth + 1);
-      }
-
-      if (LeftOf(node)) {
-        out << std::string(depth * kTextFieldWidth, ' ');
-        std::stringstream label;
-        label << std::setfill('-') << std::left << std::setw(kTextFieldWidth)
-              << WeightOf(LeftOf(node));
-        out << label.str();
-        PrintTreeInternal(out, LeftOf(node), depth + 1);
-      }
-    }
-  }
-
-  // Traverses the Huffman tree and saves paths to the leaves as bit
-  // sequences to encoding_table_.
-  void CreateEncodingTable() {
-    struct Context {
-      Context(uint32_t in_node, uint64_t in_bits, size_t in_depth)
-          : node(in_node), bits(in_bits), depth(in_depth) {}
-      uint32_t node;
-      // Huffman tree depth cannot exceed 64 as histogramm counts are expected
-      // to be positive and limited by numeric_limits<uint32_t>::max().
-      // For practical applications tree depth would be much smaller than 64.
-      uint64_t bits;
-      size_t depth;
-    };
-
-    std::queue<Context> queue;
-    queue.emplace(root_, 0, 0);
-
-    while (!queue.empty()) {
-      const Context& context = queue.front();
-      const uint32_t node = context.node;
-      const uint64_t bits = context.bits;
-      const size_t depth = context.depth;
-      queue.pop();
-
-      if (!RightOf(node) && !LeftOf(node)) {
-        auto insertion_result = encoding_table_.emplace(
-            ValueOf(node), std::pair<uint64_t, size_t>(bits, depth));
-        assert(insertion_result.second);
-        (void)insertion_result;
-      } else {
-        if (LeftOf(node)) queue.emplace(LeftOf(node), bits, depth + 1);
-
-        if (RightOf(node))
-          queue.emplace(RightOf(node), bits | (1ULL << depth), depth + 1);
-      }
-    }
-  }
-
-  // Creates new Huffman tree node and stores it in the deleter array.
-  uint32_t CreateNode() {
-    const uint32_t handle = static_cast<uint32_t>(nodes_.size());
-    nodes_.emplace_back(Node());
-    nodes_.back().id = next_node_id_++;
-    return handle;
-  }
-
-  // Huffman tree root handle.
-  uint32_t root_ = 0;
-
-  // Huffman tree deleter.
-  std::vector<Node> nodes_;
-
-  // Encoding table value -> {bits, num_bits}.
-  // Huffman codes are expected to never exceed 64 bit length (this is in fact
-  // impossible if frequencies are stored as uint32_t).
-  std::unordered_map<Val, std::pair<uint64_t, size_t>> encoding_table_;
-
-  // Next node id issued by CreateNode();
-  uint32_t next_node_id_ = 1;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_HUFFMAN_CODEC_H_
diff --git a/source/comp/markv.cpp b/source/comp/markv.cpp
deleted file mode 100644
index 736bc51..0000000
--- a/source/comp/markv.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/markv.h"
-
-#include "source/comp/markv_decoder.h"
-#include "source/comp/markv_encoder.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-spv_result_t EncodeHeader(void* user_data, spv_endianness_t endian,
-                          uint32_t magic, uint32_t version, uint32_t generator,
-                          uint32_t id_bound, uint32_t schema) {
-  MarkvEncoder* encoder = reinterpret_cast<MarkvEncoder*>(user_data);
-  return encoder->EncodeHeader(endian, magic, version, generator, id_bound,
-                               schema);
-}
-
-spv_result_t EncodeInstruction(void* user_data,
-                               const spv_parsed_instruction_t* inst) {
-  MarkvEncoder* encoder = reinterpret_cast<MarkvEncoder*>(user_data);
-  return encoder->EncodeInstruction(*inst);
-}
-
-}  // namespace
-
-spv_result_t SpirvToMarkv(
-    spv_const_context context, const std::vector<uint32_t>& spirv,
-    const MarkvCodecOptions& options, const MarkvModel& markv_model,
-    MessageConsumer message_consumer, MarkvLogConsumer log_consumer,
-    MarkvDebugConsumer debug_consumer, std::vector<uint8_t>* markv) {
-  spv_context_t hijack_context = *context;
-  SetContextMessageConsumer(&hijack_context, message_consumer);
-
-  spv_validator_options validator_options =
-      MarkvDecoder::GetValidatorOptions(options);
-  if (validator_options) {
-    spv_const_binary_t spirv_binary = {spirv.data(), spirv.size()};
-    const spv_result_t result = spvValidateWithOptions(
-        &hijack_context, validator_options, &spirv_binary, nullptr);
-    if (result != SPV_SUCCESS) return result;
-  }
-
-  MarkvEncoder encoder(&hijack_context, options, &markv_model);
-
-  spv_position_t position = {};
-  if (log_consumer || debug_consumer) {
-    encoder.CreateLogger(log_consumer, debug_consumer);
-
-    spv_text text = nullptr;
-    if (spvBinaryToText(&hijack_context, spirv.data(), spirv.size(),
-                        SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, &text,
-                        nullptr) != SPV_SUCCESS) {
-      return DiagnosticStream(position, hijack_context.consumer, "",
-                              SPV_ERROR_INVALID_BINARY)
-             << "Failed to disassemble SPIR-V binary.";
-    }
-    assert(text);
-    encoder.SetDisassembly(std::string(text->str, text->length));
-    spvTextDestroy(text);
-  }
-
-  if (spvBinaryParse(&hijack_context, &encoder, spirv.data(), spirv.size(),
-                     EncodeHeader, EncodeInstruction, nullptr) != SPV_SUCCESS) {
-    return DiagnosticStream(position, hijack_context.consumer, "",
-                            SPV_ERROR_INVALID_BINARY)
-           << "Unable to encode to MARK-V.";
-  }
-
-  *markv = encoder.GetMarkvBinary();
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvToSpirv(
-    spv_const_context context, const std::vector<uint8_t>& markv,
-    const MarkvCodecOptions& options, const MarkvModel& markv_model,
-    MessageConsumer message_consumer, MarkvLogConsumer log_consumer,
-    MarkvDebugConsumer debug_consumer, std::vector<uint32_t>* spirv) {
-  spv_position_t position = {};
-  spv_context_t hijack_context = *context;
-  SetContextMessageConsumer(&hijack_context, message_consumer);
-
-  MarkvDecoder decoder(&hijack_context, markv, options, &markv_model);
-
-  if (log_consumer || debug_consumer)
-    decoder.CreateLogger(log_consumer, debug_consumer);
-
-  if (decoder.DecodeModule(spirv) != SPV_SUCCESS) {
-    return DiagnosticStream(position, hijack_context.consumer, "",
-                            SPV_ERROR_INVALID_BINARY)
-           << "Unable to decode MARK-V.";
-  }
-
-  assert(!spirv->empty());
-  return SPV_SUCCESS;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/markv.h b/source/comp/markv.h
deleted file mode 100644
index 587086f..0000000
--- a/source/comp/markv.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// MARK-V is a compression format for SPIR-V binaries. It strips away
-// non-essential information (such as result ids which can be regenerated) and
-// uses various bit reduction techiniques to reduce the size of the binary and
-// make it more similar to other compressed SPIR-V files to further improve
-// compression of the dataset.
-
-#ifndef SOURCE_COMP_MARKV_H_
-#define SOURCE_COMP_MARKV_H_
-
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-namespace comp {
-
-class MarkvModel;
-
-struct MarkvCodecOptions {
-  bool validate_spirv_binary = false;
-};
-
-// Debug callback. Called once per instruction.
-// |words| is instruction SPIR-V words.
-// |bits| is a textual representation of the MARK-V bit sequence used to encode
-// the instruction (char '0' for 0, char '1' for 1).
-// |comment| contains all logs generated while processing the instruction.
-using MarkvDebugConsumer =
-    std::function<bool(const std::vector<uint32_t>& words,
-                       const std::string& bits, const std::string& comment)>;
-
-// Logging callback. Called often (if decoder reads a single bit, the log
-// consumer will receive 1 character string with that bit).
-// This callback is more suitable for continous output than MarkvDebugConsumer,
-// for example if the codec crashes it would allow to pinpoint on which operand
-// or bit the crash happened.
-// |snippet| could be any atomic fragment of text logged by the codec. It can
-// contain a paragraph of text with newlines, or can be just one character.
-using MarkvLogConsumer = std::function<void(const std::string& snippet)>;
-
-// Encodes the given SPIR-V binary to MARK-V binary.
-// |log_consumer| is optional (pass MarkvLogConsumer() to disable).
-// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable).
-spv_result_t SpirvToMarkv(
-    spv_const_context context, const std::vector<uint32_t>& spirv,
-    const MarkvCodecOptions& options, const MarkvModel& markv_model,
-    MessageConsumer message_consumer, MarkvLogConsumer log_consumer,
-    MarkvDebugConsumer debug_consumer, std::vector<uint8_t>* markv);
-
-// Decodes a SPIR-V binary from the given MARK-V binary.
-// |log_consumer| is optional (pass MarkvLogConsumer() to disable).
-// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable).
-spv_result_t MarkvToSpirv(
-    spv_const_context context, const std::vector<uint8_t>& markv,
-    const MarkvCodecOptions& options, const MarkvModel& markv_model,
-    MessageConsumer message_consumer, MarkvLogConsumer log_consumer,
-    MarkvDebugConsumer debug_consumer, std::vector<uint32_t>* spirv);
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_H_
diff --git a/source/comp/markv_codec.cpp b/source/comp/markv_codec.cpp
deleted file mode 100644
index ae3ce79..0000000
--- a/source/comp/markv_codec.cpp
+++ /dev/null
@@ -1,793 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// MARK-V is a compression format for SPIR-V binaries. It strips away
-// non-essential information (such as result IDs which can be regenerated) and
-// uses various bit reduction techniques to reduce the size of the binary.
-
-#include "source/comp/markv_codec.h"
-
-#include "source/comp/markv_logger.h"
-#include "source/latest_version_glsl_std_450_header.h"
-#include "source/latest_version_opencl_std_header.h"
-#include "source/opcode.h"
-#include "source/util/make_unique.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-// Custom hash function used to produce short descriptors.
-uint32_t ShortHashU32Array(const std::vector<uint32_t>& words) {
-  // The hash function is a sum of hashes of each word seeded by word index.
-  // Knuth's multiplicative hash is used to hash the words.
-  const uint32_t kKnuthMulHash = 2654435761;
-  uint32_t val = 0;
-  for (uint32_t i = 0; i < words.size(); ++i) {
-    val += (words[i] + i + 123) * kKnuthMulHash;
-  }
-  return 1 + val % ((1 << MarkvCodec::kShortDescriptorNumBits) - 1);
-}
-
-// Returns a set of mtf rank codecs based on a plausible hand-coded
-// distribution.
-std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>>
-GetMtfHuffmanCodecs() {
-  std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>> codecs;
-
-  std::unique_ptr<HuffmanCodec<uint32_t>> codec;
-
-  codec = MakeUnique<HuffmanCodec<uint32_t>>(std::map<uint32_t, uint32_t>({
-      {0, 5},
-      {1, 40},
-      {2, 10},
-      {3, 5},
-      {4, 5},
-      {5, 5},
-      {6, 3},
-      {7, 3},
-      {8, 3},
-      {9, 3},
-      {MarkvCodec::kMtfRankEncodedByValueSignal, 10},
-  }));
-  codecs.emplace(kMtfAll, std::move(codec));
-
-  codec = MakeUnique<HuffmanCodec<uint32_t>>(std::map<uint32_t, uint32_t>({
-      {1, 50},
-      {2, 20},
-      {3, 5},
-      {4, 5},
-      {5, 2},
-      {6, 1},
-      {7, 1},
-      {8, 1},
-      {9, 1},
-      {MarkvCodec::kMtfRankEncodedByValueSignal, 10},
-  }));
-  codecs.emplace(kMtfGenericNonZeroRank, std::move(codec));
-
-  return codecs;
-}
-
-}  // namespace
-
-const uint32_t MarkvCodec::kMarkvMagicNumber = 0x07230303;
-
-const uint32_t MarkvCodec::kMtfSmallestRankEncodedByValue = 10;
-
-const uint32_t MarkvCodec::kMtfRankEncodedByValueSignal =
-    std::numeric_limits<uint32_t>::max();
-
-const uint32_t MarkvCodec::kShortDescriptorNumBits = 8;
-
-const size_t MarkvCodec::kByteBreakAfterInstIfLessThanUntilNextByte = 8;
-
-MarkvCodec::MarkvCodec(spv_const_context context,
-                       spv_validator_options validator_options,
-                       const MarkvModel* model)
-    : validator_options_(validator_options),
-      grammar_(context),
-      model_(model),
-      short_id_descriptors_(ShortHashU32Array),
-      mtf_huffman_codecs_(GetMtfHuffmanCodecs()),
-      context_(context) {}
-
-MarkvCodec::~MarkvCodec() { spvValidatorOptionsDestroy(validator_options_); }
-
-MarkvCodec::MarkvHeader::MarkvHeader()
-    : magic_number(MarkvCodec::kMarkvMagicNumber),
-      markv_version(MarkvCodec::GetMarkvVersion()) {}
-
-// Defines and returns current MARK-V version.
-// static
-uint32_t MarkvCodec::GetMarkvVersion() {
-  const uint32_t kVersionMajor = 1;
-  const uint32_t kVersionMinor = 4;
-  return kVersionMinor | (kVersionMajor << 16);
-}
-
-size_t MarkvCodec::GetNumBitsToNextByte(size_t bit_pos) const {
-  return (8 - (bit_pos % 8)) % 8;
-}
-
-// Returns true if the opcode has a fixed number of operands. May return a
-// false negative.
-bool MarkvCodec::OpcodeHasFixedNumberOfOperands(SpvOp opcode) const {
-  switch (opcode) {
-    // TODO(atgoo@github.com) This is not a complete list.
-    case SpvOpNop:
-    case SpvOpName:
-    case SpvOpUndef:
-    case SpvOpSizeOf:
-    case SpvOpLine:
-    case SpvOpNoLine:
-    case SpvOpDecorationGroup:
-    case SpvOpExtension:
-    case SpvOpExtInstImport:
-    case SpvOpMemoryModel:
-    case SpvOpCapability:
-    case SpvOpTypeVoid:
-    case SpvOpTypeBool:
-    case SpvOpTypeInt:
-    case SpvOpTypeFloat:
-    case SpvOpTypeVector:
-    case SpvOpTypeMatrix:
-    case SpvOpTypeSampler:
-    case SpvOpTypeSampledImage:
-    case SpvOpTypeArray:
-    case SpvOpTypePointer:
-    case SpvOpConstantTrue:
-    case SpvOpConstantFalse:
-    case SpvOpLabel:
-    case SpvOpBranch:
-    case SpvOpFunction:
-    case SpvOpFunctionParameter:
-    case SpvOpFunctionEnd:
-    case SpvOpBitcast:
-    case SpvOpCopyObject:
-    case SpvOpTranspose:
-    case SpvOpSNegate:
-    case SpvOpFNegate:
-    case SpvOpIAdd:
-    case SpvOpFAdd:
-    case SpvOpISub:
-    case SpvOpFSub:
-    case SpvOpIMul:
-    case SpvOpFMul:
-    case SpvOpUDiv:
-    case SpvOpSDiv:
-    case SpvOpFDiv:
-    case SpvOpUMod:
-    case SpvOpSRem:
-    case SpvOpSMod:
-    case SpvOpFRem:
-    case SpvOpFMod:
-    case SpvOpVectorTimesScalar:
-    case SpvOpMatrixTimesScalar:
-    case SpvOpVectorTimesMatrix:
-    case SpvOpMatrixTimesVector:
-    case SpvOpMatrixTimesMatrix:
-    case SpvOpOuterProduct:
-    case SpvOpDot:
-      return true;
-    default:
-      break;
-  }
-  return false;
-}
-
-void MarkvCodec::ProcessCurInstruction() {
-  instructions_.emplace_back(new val::Instruction(&inst_));
-
-  const SpvOp opcode = SpvOp(inst_.opcode);
-
-  if (inst_.result_id) {
-    id_to_def_instruction_.emplace(inst_.result_id, instructions_.back().get());
-
-    // Collect ids local to the current function.
-    if (cur_function_id_) {
-      ids_local_to_cur_function_.push_back(inst_.result_id);
-    }
-
-    // Starting new function.
-    if (opcode == SpvOpFunction) {
-      cur_function_id_ = inst_.result_id;
-      cur_function_return_type_ = inst_.type_id;
-      if (model_->id_fallback_strategy() ==
-          MarkvModel::IdFallbackStrategy::kRuleBased) {
-        multi_mtf_.Insert(GetMtfFunctionWithReturnType(inst_.type_id),
-                          inst_.result_id);
-      }
-
-      // Store function parameter types in a queue, so that we know which types
-      // to expect in the following OpFunctionParameter instructions.
-      const val::Instruction* def_inst = FindDef(inst_.words[4]);
-      assert(def_inst);
-      assert(def_inst->opcode() == SpvOpTypeFunction);
-      for (uint32_t i = 3; i < def_inst->words().size(); ++i) {
-        remaining_function_parameter_types_.push_back(def_inst->word(i));
-      }
-    }
-  }
-
-  // Remove local ids from MTFs if function end.
-  if (opcode == SpvOpFunctionEnd) {
-    cur_function_id_ = 0;
-    for (uint32_t id : ids_local_to_cur_function_) multi_mtf_.RemoveFromAll(id);
-    ids_local_to_cur_function_.clear();
-    assert(remaining_function_parameter_types_.empty());
-  }
-
-  if (!inst_.result_id) return;
-
-  {
-    // Save the result ID to type ID mapping.
-    // In the grammar, type ID always appears before result ID.
-    // A regular value maps to its type. Some instructions (e.g. OpLabel)
-    // have no type Id, and will map to 0. The result Id for a
-    // type-generating instruction (e.g. OpTypeInt) maps to itself.
-    auto insertion_result = id_to_type_id_.emplace(
-        inst_.result_id, spvOpcodeGeneratesType(SpvOp(inst_.opcode))
-                             ? inst_.result_id
-                             : inst_.type_id);
-    (void)insertion_result;
-    assert(insertion_result.second);
-  }
-
-  // Add result_id to MTFs.
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kRuleBased) {
-    switch (opcode) {
-      case SpvOpTypeFloat:
-      case SpvOpTypeInt:
-      case SpvOpTypeBool:
-      case SpvOpTypeVector:
-      case SpvOpTypePointer:
-      case SpvOpExtInstImport:
-      case SpvOpTypeSampledImage:
-      case SpvOpTypeImage:
-      case SpvOpTypeSampler:
-        multi_mtf_.Insert(GetMtfIdGeneratedByOpcode(opcode), inst_.result_id);
-        break;
-      default:
-        break;
-    }
-
-    if (spvOpcodeIsComposite(opcode)) {
-      multi_mtf_.Insert(kMtfTypeComposite, inst_.result_id);
-    }
-
-    if (opcode == SpvOpLabel) {
-      multi_mtf_.InsertOrPromote(kMtfLabel, inst_.result_id);
-    }
-
-    if (opcode == SpvOpTypeInt) {
-      multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
-      multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id);
-    }
-
-    if (opcode == SpvOpTypeFloat) {
-      multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
-      multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id);
-    }
-
-    if (opcode == SpvOpTypeBool) {
-      multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id);
-      multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id);
-    }
-
-    if (opcode == SpvOpTypeVector) {
-      const uint32_t component_type_id = inst_.words[2];
-      const uint32_t size = inst_.words[3];
-      if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeFloat),
-                              component_type_id)) {
-        multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id);
-      } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeInt),
-                                     component_type_id)) {
-        multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id);
-      } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeBool),
-                                     component_type_id)) {
-        multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id);
-      }
-      multi_mtf_.Insert(GetMtfTypeVectorOfSize(size), inst_.result_id);
-    }
-
-    if (inst_.opcode == SpvOpTypeFunction) {
-      const uint32_t return_type = inst_.words[2];
-      multi_mtf_.Insert(kMtfTypeReturnedByFunction, return_type);
-      multi_mtf_.Insert(GetMtfFunctionTypeWithReturnType(return_type),
-                        inst_.result_id);
-    }
-
-    if (inst_.type_id) {
-      const val::Instruction* type_inst = FindDef(inst_.type_id);
-      assert(type_inst);
-
-      multi_mtf_.Insert(kMtfObject, inst_.result_id);
-
-      multi_mtf_.Insert(GetMtfIdOfType(inst_.type_id), inst_.result_id);
-
-      if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, inst_.type_id)) {
-        multi_mtf_.Insert(kMtfFloatScalarOrVector, inst_.result_id);
-      }
-
-      if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, inst_.type_id))
-        multi_mtf_.Insert(kMtfIntScalarOrVector, inst_.result_id);
-
-      if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, inst_.type_id))
-        multi_mtf_.Insert(kMtfBoolScalarOrVector, inst_.result_id);
-
-      if (multi_mtf_.HasValue(kMtfTypeComposite, inst_.type_id))
-        multi_mtf_.Insert(kMtfComposite, inst_.result_id);
-
-      switch (type_inst->opcode()) {
-        case SpvOpTypeInt:
-        case SpvOpTypeBool:
-        case SpvOpTypePointer:
-        case SpvOpTypeVector:
-        case SpvOpTypeImage:
-        case SpvOpTypeSampledImage:
-        case SpvOpTypeSampler:
-          multi_mtf_.Insert(
-              GetMtfIdWithTypeGeneratedByOpcode(type_inst->opcode()),
-              inst_.result_id);
-          break;
-        default:
-          break;
-      }
-
-      if (type_inst->opcode() == SpvOpTypeVector) {
-        const uint32_t component_type = type_inst->word(2);
-        multi_mtf_.Insert(GetMtfVectorOfComponentType(component_type),
-                          inst_.result_id);
-      }
-
-      if (type_inst->opcode() == SpvOpTypePointer) {
-        assert(type_inst->operands().size() > 2);
-        assert(type_inst->words().size() > type_inst->operands()[2].offset);
-        const uint32_t data_type =
-            type_inst->word(type_inst->operands()[2].offset);
-        multi_mtf_.Insert(GetMtfPointerToType(data_type), inst_.result_id);
-
-        if (multi_mtf_.HasValue(kMtfTypeComposite, data_type))
-          multi_mtf_.Insert(kMtfTypePointerToComposite, inst_.result_id);
-      }
-    }
-
-    if (spvOpcodeGeneratesType(opcode)) {
-      if (opcode != SpvOpTypeFunction) {
-        multi_mtf_.Insert(kMtfTypeNonFunction, inst_.result_id);
-      }
-    }
-  }
-
-  if (model_->AnyDescriptorHasCodingScheme()) {
-    const uint32_t long_descriptor =
-        long_id_descriptors_.ProcessInstruction(inst_);
-    if (model_->DescriptorHasCodingScheme(long_descriptor))
-      multi_mtf_.Insert(GetMtfLongIdDescriptor(long_descriptor),
-                        inst_.result_id);
-  }
-
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kShortDescriptor) {
-    const uint32_t short_descriptor =
-        short_id_descriptors_.ProcessInstruction(inst_);
-    multi_mtf_.Insert(GetMtfShortIdDescriptor(short_descriptor),
-                      inst_.result_id);
-  }
-}
-
-uint64_t MarkvCodec::GetRuleBasedMtf() {
-  // This function is only called for id operands (but not result ids).
-  assert(spvIsIdType(operand_.type) ||
-         operand_.type == SPV_OPERAND_TYPE_OPTIONAL_ID);
-  assert(operand_.type != SPV_OPERAND_TYPE_RESULT_ID);
-
-  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
-
-  // All operand slots which expect label id.
-  if ((inst_.opcode == SpvOpLoopMerge && operand_index_ <= 1) ||
-      (inst_.opcode == SpvOpSelectionMerge && operand_index_ == 0) ||
-      (inst_.opcode == SpvOpBranch && operand_index_ == 0) ||
-      (inst_.opcode == SpvOpBranchConditional &&
-       (operand_index_ == 1 || operand_index_ == 2)) ||
-      (inst_.opcode == SpvOpPhi && operand_index_ >= 3 &&
-       operand_index_ % 2 == 1) ||
-      (inst_.opcode == SpvOpSwitch && operand_index_ > 0)) {
-    return kMtfLabel;
-  }
-
-  switch (opcode) {
-    case SpvOpFAdd:
-    case SpvOpFSub:
-    case SpvOpFMul:
-    case SpvOpFDiv:
-    case SpvOpFRem:
-    case SpvOpFMod:
-    case SpvOpFNegate: {
-      if (operand_index_ == 0) return kMtfTypeFloatScalarOrVector;
-      return GetMtfIdOfType(inst_.type_id);
-    }
-
-    case SpvOpISub:
-    case SpvOpIAdd:
-    case SpvOpIMul:
-    case SpvOpSDiv:
-    case SpvOpUDiv:
-    case SpvOpSMod:
-    case SpvOpUMod:
-    case SpvOpSRem:
-    case SpvOpSNegate: {
-      if (operand_index_ == 0) return kMtfTypeIntScalarOrVector;
-
-      return kMtfIntScalarOrVector;
-    }
-
-      // TODO(atgoo@github.com) Add OpConvertFToU and other opcodes.
-
-    case SpvOpFOrdEqual:
-    case SpvOpFUnordEqual:
-    case SpvOpFOrdNotEqual:
-    case SpvOpFUnordNotEqual:
-    case SpvOpFOrdLessThan:
-    case SpvOpFUnordLessThan:
-    case SpvOpFOrdGreaterThan:
-    case SpvOpFUnordGreaterThan:
-    case SpvOpFOrdLessThanEqual:
-    case SpvOpFUnordLessThanEqual:
-    case SpvOpFOrdGreaterThanEqual:
-    case SpvOpFUnordGreaterThanEqual: {
-      if (operand_index_ == 0) return kMtfTypeBoolScalarOrVector;
-      if (operand_index_ == 2) return kMtfFloatScalarOrVector;
-      if (operand_index_ == 3) {
-        const uint32_t first_operand_id = GetInstWords()[3];
-        const uint32_t first_operand_type = id_to_type_id_.at(first_operand_id);
-        return GetMtfIdOfType(first_operand_type);
-      }
-      break;
-    }
-
-    case SpvOpVectorShuffle: {
-      if (operand_index_ == 0) {
-        assert(inst_.num_operands > 4);
-        return GetMtfTypeVectorOfSize(inst_.num_operands - 4);
-      }
-
-      assert(inst_.type_id);
-      if (operand_index_ == 2 || operand_index_ == 3)
-        return GetMtfVectorOfComponentType(
-            GetVectorComponentType(inst_.type_id));
-      break;
-    }
-
-    case SpvOpVectorTimesScalar: {
-      if (operand_index_ == 0) {
-        // TODO(atgoo@github.com) Could be narrowed to vector of floats.
-        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
-      }
-
-      assert(inst_.type_id);
-      if (operand_index_ == 2) return GetMtfIdOfType(inst_.type_id);
-      if (operand_index_ == 3)
-        return GetMtfIdOfType(GetVectorComponentType(inst_.type_id));
-      break;
-    }
-
-    case SpvOpDot: {
-      if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypeFloat);
-
-      assert(inst_.type_id);
-      if (operand_index_ == 2)
-        return GetMtfVectorOfComponentType(inst_.type_id);
-      if (operand_index_ == 3) {
-        const uint32_t vector_id = GetInstWords()[3];
-        const uint32_t vector_type = id_to_type_id_.at(vector_id);
-        return GetMtfIdOfType(vector_type);
-      }
-      break;
-    }
-
-    case SpvOpTypeVector: {
-      if (operand_index_ == 1) {
-        return kMtfTypeScalar;
-      }
-      break;
-    }
-
-    case SpvOpTypeMatrix: {
-      if (operand_index_ == 1) {
-        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
-      }
-      break;
-    }
-
-    case SpvOpTypePointer: {
-      if (operand_index_ == 2) {
-        return kMtfTypeNonFunction;
-      }
-      break;
-    }
-
-    case SpvOpTypeStruct: {
-      if (operand_index_ >= 1) {
-        return kMtfTypeNonFunction;
-      }
-      break;
-    }
-
-    case SpvOpTypeFunction: {
-      if (operand_index_ == 1) {
-        return kMtfTypeNonFunction;
-      }
-
-      if (operand_index_ >= 2) {
-        return kMtfTypeNonFunction;
-      }
-      break;
-    }
-
-    case SpvOpLoad: {
-      if (operand_index_ == 0) return kMtfTypeNonFunction;
-
-      if (operand_index_ == 2) {
-        assert(inst_.type_id);
-        return GetMtfPointerToType(inst_.type_id);
-      }
-      break;
-    }
-
-    case SpvOpStore: {
-      if (operand_index_ == 0)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypePointer);
-      if (operand_index_ == 1) {
-        const uint32_t pointer_id = GetInstWords()[1];
-        const uint32_t pointer_type = id_to_type_id_.at(pointer_id);
-        const val::Instruction* pointer_inst = FindDef(pointer_type);
-        assert(pointer_inst);
-        assert(pointer_inst->opcode() == SpvOpTypePointer);
-        const uint32_t data_type =
-            pointer_inst->word(pointer_inst->operands()[2].offset);
-        return GetMtfIdOfType(data_type);
-      }
-      break;
-    }
-
-    case SpvOpVariable: {
-      if (operand_index_ == 0)
-        return GetMtfIdGeneratedByOpcode(SpvOpTypePointer);
-      break;
-    }
-
-    case SpvOpAccessChain: {
-      if (operand_index_ == 0)
-        return GetMtfIdGeneratedByOpcode(SpvOpTypePointer);
-      if (operand_index_ == 2) return kMtfTypePointerToComposite;
-      if (operand_index_ >= 3)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeInt);
-      break;
-    }
-
-    case SpvOpCompositeConstruct: {
-      if (operand_index_ == 0) return kMtfTypeComposite;
-      if (operand_index_ >= 2) {
-        const uint32_t composite_type = GetInstWords()[1];
-        if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, composite_type))
-          return kMtfFloatScalarOrVector;
-        if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, composite_type))
-          return kMtfIntScalarOrVector;
-        if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, composite_type))
-          return kMtfBoolScalarOrVector;
-      }
-      break;
-    }
-
-    case SpvOpCompositeExtract: {
-      if (operand_index_ == 2) return kMtfComposite;
-      break;
-    }
-
-    case SpvOpConstantComposite: {
-      if (operand_index_ == 0) return kMtfTypeComposite;
-      if (operand_index_ >= 2) {
-        const val::Instruction* composite_type_inst = FindDef(inst_.type_id);
-        assert(composite_type_inst);
-        if (composite_type_inst->opcode() == SpvOpTypeVector) {
-          return GetMtfIdOfType(composite_type_inst->word(2));
-        }
-      }
-      break;
-    }
-
-    case SpvOpExtInst: {
-      if (operand_index_ == 2)
-        return GetMtfIdGeneratedByOpcode(SpvOpExtInstImport);
-      if (operand_index_ >= 4) {
-        const uint32_t return_type = GetInstWords()[1];
-        const uint32_t ext_inst_type = inst_.ext_inst_type;
-        const uint32_t ext_inst_index = GetInstWords()[4];
-        // TODO(atgoo@github.com) The list of extended instructions is
-        // incomplete. Only common instructions and low-hanging fruits listed.
-        if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) {
-          switch (ext_inst_index) {
-            case GLSLstd450FAbs:
-            case GLSLstd450FClamp:
-            case GLSLstd450FMax:
-            case GLSLstd450FMin:
-            case GLSLstd450FMix:
-            case GLSLstd450Step:
-            case GLSLstd450SmoothStep:
-            case GLSLstd450Fma:
-            case GLSLstd450Pow:
-            case GLSLstd450Exp:
-            case GLSLstd450Exp2:
-            case GLSLstd450Log:
-            case GLSLstd450Log2:
-            case GLSLstd450Sqrt:
-            case GLSLstd450InverseSqrt:
-            case GLSLstd450Fract:
-            case GLSLstd450Floor:
-            case GLSLstd450Ceil:
-            case GLSLstd450Radians:
-            case GLSLstd450Degrees:
-            case GLSLstd450Sin:
-            case GLSLstd450Cos:
-            case GLSLstd450Tan:
-            case GLSLstd450Sinh:
-            case GLSLstd450Cosh:
-            case GLSLstd450Tanh:
-            case GLSLstd450Asin:
-            case GLSLstd450Acos:
-            case GLSLstd450Atan:
-            case GLSLstd450Atan2:
-            case GLSLstd450Asinh:
-            case GLSLstd450Acosh:
-            case GLSLstd450Atanh:
-            case GLSLstd450MatrixInverse:
-            case GLSLstd450Cross:
-            case GLSLstd450Normalize:
-            case GLSLstd450Reflect:
-            case GLSLstd450FaceForward:
-              return GetMtfIdOfType(return_type);
-            case GLSLstd450Length:
-            case GLSLstd450Distance:
-            case GLSLstd450Refract:
-              return kMtfFloatScalarOrVector;
-            default:
-              break;
-          }
-        } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) {
-          switch (ext_inst_index) {
-            case OpenCLLIB::Fabs:
-            case OpenCLLIB::FClamp:
-            case OpenCLLIB::Fmax:
-            case OpenCLLIB::Fmin:
-            case OpenCLLIB::Step:
-            case OpenCLLIB::Smoothstep:
-            case OpenCLLIB::Fma:
-            case OpenCLLIB::Pow:
-            case OpenCLLIB::Exp:
-            case OpenCLLIB::Exp2:
-            case OpenCLLIB::Log:
-            case OpenCLLIB::Log2:
-            case OpenCLLIB::Sqrt:
-            case OpenCLLIB::Rsqrt:
-            case OpenCLLIB::Fract:
-            case OpenCLLIB::Floor:
-            case OpenCLLIB::Ceil:
-            case OpenCLLIB::Radians:
-            case OpenCLLIB::Degrees:
-            case OpenCLLIB::Sin:
-            case OpenCLLIB::Cos:
-            case OpenCLLIB::Tan:
-            case OpenCLLIB::Sinh:
-            case OpenCLLIB::Cosh:
-            case OpenCLLIB::Tanh:
-            case OpenCLLIB::Asin:
-            case OpenCLLIB::Acos:
-            case OpenCLLIB::Atan:
-            case OpenCLLIB::Atan2:
-            case OpenCLLIB::Asinh:
-            case OpenCLLIB::Acosh:
-            case OpenCLLIB::Atanh:
-            case OpenCLLIB::Cross:
-            case OpenCLLIB::Normalize:
-              return GetMtfIdOfType(return_type);
-            case OpenCLLIB::Length:
-            case OpenCLLIB::Distance:
-              return kMtfFloatScalarOrVector;
-            default:
-              break;
-          }
-        }
-      }
-      break;
-    }
-
-    case SpvOpFunction: {
-      if (operand_index_ == 0) return kMtfTypeReturnedByFunction;
-
-      if (operand_index_ == 3) {
-        const uint32_t return_type = GetInstWords()[1];
-        return GetMtfFunctionTypeWithReturnType(return_type);
-      }
-      break;
-    }
-
-    case SpvOpFunctionCall: {
-      if (operand_index_ == 0) return kMtfTypeReturnedByFunction;
-
-      if (operand_index_ == 2) {
-        const uint32_t return_type = GetInstWords()[1];
-        return GetMtfFunctionWithReturnType(return_type);
-      }
-
-      if (operand_index_ >= 3) {
-        const uint32_t function_id = GetInstWords()[3];
-        const val::Instruction* function_inst = FindDef(function_id);
-        if (!function_inst) return kMtfObject;
-
-        assert(function_inst->opcode() == SpvOpFunction);
-
-        const uint32_t function_type_id = function_inst->word(4);
-        const val::Instruction* function_type_inst = FindDef(function_type_id);
-        assert(function_type_inst);
-        assert(function_type_inst->opcode() == SpvOpTypeFunction);
-
-        const uint32_t argument_type = function_type_inst->word(operand_index_);
-        return GetMtfIdOfType(argument_type);
-      }
-      break;
-    }
-
-    case SpvOpReturnValue: {
-      if (operand_index_ == 0) return GetMtfIdOfType(cur_function_return_type_);
-      break;
-    }
-
-    case SpvOpBranchConditional: {
-      if (operand_index_ == 0)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeBool);
-      break;
-    }
-
-    case SpvOpSampledImage: {
-      if (operand_index_ == 0)
-        return GetMtfIdGeneratedByOpcode(SpvOpTypeSampledImage);
-      if (operand_index_ == 2)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeImage);
-      if (operand_index_ == 3)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampler);
-      break;
-    }
-
-    case SpvOpImageSampleImplicitLod: {
-      if (operand_index_ == 0)
-        return GetMtfIdGeneratedByOpcode(SpvOpTypeVector);
-      if (operand_index_ == 2)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampledImage);
-      if (operand_index_ == 3)
-        return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeVector);
-      break;
-    }
-
-    default:
-      break;
-  }
-
-  return kMtfNone;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/markv_codec.h b/source/comp/markv_codec.h
deleted file mode 100644
index f313d61..0000000
--- a/source/comp/markv_codec.h
+++ /dev/null
@@ -1,337 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_COMP_MARKV_CODEC_H_
-#define SOURCE_COMP_MARKV_CODEC_H_
-
-#include <list>
-#include <map>
-#include <memory>
-#include <vector>
-
-#include "source/assembly_grammar.h"
-#include "source/comp/huffman_codec.h"
-#include "source/comp/markv_model.h"
-#include "source/comp/move_to_front.h"
-#include "source/diagnostic.h"
-#include "source/id_descriptor.h"
-
-#include "source/val/instruction.h"
-
-// Base class for MARK-V encoder and decoder. Contains common functionality
-// such as:
-// - Validator connection and validation state.
-// - SPIR-V grammar and helper functions.
-
-namespace spvtools {
-namespace comp {
-
-class MarkvLogger;
-
-// Handles for move-to-front sequences. Enums which end with "Begin" define
-// handle spaces which start at that value and span 16 or 32 bit wide.
-enum : uint64_t {
-  kMtfNone = 0,
-  // All ids.
-  kMtfAll,
-  // All forward declared ids.
-  kMtfForwardDeclared,
-  // All type ids except for generated by OpTypeFunction.
-  kMtfTypeNonFunction,
-  // All labels.
-  kMtfLabel,
-  // All ids created by instructions which had type_id.
-  kMtfObject,
-  // All types generated by OpTypeFloat, OpTypeInt, OpTypeBool.
-  kMtfTypeScalar,
-  // All composite types.
-  kMtfTypeComposite,
-  // Boolean type or any vector type of it.
-  kMtfTypeBoolScalarOrVector,
-  // All float types or any vector floats type.
-  kMtfTypeFloatScalarOrVector,
-  // All int types or any vector int type.
-  kMtfTypeIntScalarOrVector,
-  // All types declared as return types in OpTypeFunction.
-  kMtfTypeReturnedByFunction,
-  // All composite objects.
-  kMtfComposite,
-  // All bool objects or vectors of bools.
-  kMtfBoolScalarOrVector,
-  // All float objects or vectors of float.
-  kMtfFloatScalarOrVector,
-  // All int objects or vectors of int.
-  kMtfIntScalarOrVector,
-  // All pointer types which point to composited.
-  kMtfTypePointerToComposite,
-  // Used by EncodeMtfRankHuffman.
-  kMtfGenericNonZeroRank,
-  // Handle space for ids of specific type.
-  kMtfIdOfTypeBegin = 0x10000,
-  // Handle space for ids generated by specific opcode.
-  kMtfIdGeneratedByOpcode = 0x20000,
-  // Handle space for ids of objects with type generated by specific opcode.
-  kMtfIdWithTypeGeneratedByOpcodeBegin = 0x30000,
-  // All vectors of specific component type.
-  kMtfVectorOfComponentTypeBegin = 0x40000,
-  // All vector types of specific size.
-  kMtfTypeVectorOfSizeBegin = 0x50000,
-  // All pointer types to specific type.
-  kMtfPointerToTypeBegin = 0x60000,
-  // All function types which return specific type.
-  kMtfFunctionTypeWithReturnTypeBegin = 0x70000,
-  // All function objects which return specific type.
-  kMtfFunctionWithReturnTypeBegin = 0x80000,
-  // Short id descriptor space (max 16-bit).
-  kMtfShortIdDescriptorSpaceBegin = 0x90000,
-  // Long id descriptor space (32-bit).
-  kMtfLongIdDescriptorSpaceBegin = 0x100000000,
-};
-
-class MarkvCodec {
- public:
-  static const uint32_t kMarkvMagicNumber;
-
-  // Mtf ranks smaller than this are encoded with Huffman coding.
-  static const uint32_t kMtfSmallestRankEncodedByValue;
-
-  // Signals that the mtf rank is too large to be encoded with Huffman.
-  static const uint32_t kMtfRankEncodedByValueSignal;
-
-  static const uint32_t kShortDescriptorNumBits;
-
-  static const size_t kByteBreakAfterInstIfLessThanUntilNextByte;
-
-  static uint32_t GetMarkvVersion();
-
-  virtual ~MarkvCodec();
-
- protected:
-  struct MarkvHeader {
-    MarkvHeader();
-
-    uint32_t magic_number;
-    uint32_t markv_version;
-    // Magic number to identify or verify MarkvModel used for encoding.
-    uint32_t markv_model = 0;
-    uint32_t markv_length_in_bits = 0;
-    uint32_t spirv_version = 0;
-    uint32_t spirv_generator = 0;
-  };
-
-  // |model| is owned by the caller, must be not null and valid during the
-  // lifetime of the codec.
-  MarkvCodec(spv_const_context context, spv_validator_options validator_options,
-             const MarkvModel* model);
-
-  // Returns instruction which created |id| or nullptr if such instruction was
-  // not registered.
-  const val::Instruction* FindDef(uint32_t id) const {
-    const auto it = id_to_def_instruction_.find(id);
-    if (it == id_to_def_instruction_.end()) return nullptr;
-    return it->second;
-  }
-
-  size_t GetNumBitsToNextByte(size_t bit_pos) const;
-  bool OpcodeHasFixedNumberOfOperands(SpvOp opcode) const;
-
-  // Returns type id of vector type component.
-  uint32_t GetVectorComponentType(uint32_t vector_type_id) const {
-    const val::Instruction* type_inst = FindDef(vector_type_id);
-    assert(type_inst);
-    assert(type_inst->opcode() == SpvOpTypeVector);
-
-    const uint32_t component_type =
-        type_inst->word(type_inst->operands()[1].offset);
-    return component_type;
-  }
-
-  // Returns mtf handle for ids of given type.
-  uint64_t GetMtfIdOfType(uint32_t type_id) const {
-    return kMtfIdOfTypeBegin + type_id;
-  }
-
-  // Returns mtf handle for ids generated by given opcode.
-  uint64_t GetMtfIdGeneratedByOpcode(SpvOp opcode) const {
-    return kMtfIdGeneratedByOpcode + opcode;
-  }
-
-  // Returns mtf handle for ids of type generated by given opcode.
-  uint64_t GetMtfIdWithTypeGeneratedByOpcode(SpvOp opcode) const {
-    return kMtfIdWithTypeGeneratedByOpcodeBegin + opcode;
-  }
-
-  // Returns mtf handle for vectors of specific component type.
-  uint64_t GetMtfVectorOfComponentType(uint32_t type_id) const {
-    return kMtfVectorOfComponentTypeBegin + type_id;
-  }
-
-  // Returns mtf handle for vector type of specific size.
-  uint64_t GetMtfTypeVectorOfSize(uint32_t size) const {
-    return kMtfTypeVectorOfSizeBegin + size;
-  }
-
-  // Returns mtf handle for pointers to specific size.
-  uint64_t GetMtfPointerToType(uint32_t type_id) const {
-    return kMtfPointerToTypeBegin + type_id;
-  }
-
-  // Returns mtf handle for function types with given return type.
-  uint64_t GetMtfFunctionTypeWithReturnType(uint32_t type_id) const {
-    return kMtfFunctionTypeWithReturnTypeBegin + type_id;
-  }
-
-  // Returns mtf handle for functions with given return type.
-  uint64_t GetMtfFunctionWithReturnType(uint32_t type_id) const {
-    return kMtfFunctionWithReturnTypeBegin + type_id;
-  }
-
-  // Returns mtf handle for the given long id descriptor.
-  uint64_t GetMtfLongIdDescriptor(uint32_t descriptor) const {
-    return kMtfLongIdDescriptorSpaceBegin + descriptor;
-  }
-
-  // Returns mtf handle for the given short id descriptor.
-  uint64_t GetMtfShortIdDescriptor(uint32_t descriptor) const {
-    return kMtfShortIdDescriptorSpaceBegin + descriptor;
-  }
-
-  // Process data from the current instruction. This would update MTFs and
-  // other data containers.
-  void ProcessCurInstruction();
-
-  // Returns move-to-front handle to be used for the current operand slot.
-  // Mtf handle is chosen based on a set of rules defined by SPIR-V grammar.
-  uint64_t GetRuleBasedMtf();
-
-  // Returns words of the current instruction. Decoder has a different
-  // implementation and the array is valid only until the previously decoded
-  // word.
-  virtual const uint32_t* GetInstWords() const { return inst_.words; }
-
-  // Returns the opcode of the previous instruction.
-  SpvOp GetPrevOpcode() const {
-    if (instructions_.empty()) return SpvOpNop;
-
-    return instructions_.back()->opcode();
-  }
-
-  // Returns diagnostic stream, position index is set to instruction number.
-  DiagnosticStream Diag(spv_result_t error_code) const {
-    return DiagnosticStream({0, 0, instructions_.size()}, context_->consumer,
-                            "", error_code);
-  }
-
-  // Returns current id bound.
-  uint32_t GetIdBound() const { return id_bound_; }
-
-  // Sets current id bound, expected to be no lower than the previous one.
-  void SetIdBound(uint32_t id_bound) {
-    assert(id_bound >= id_bound_);
-    id_bound_ = id_bound;
-  }
-
-  // Returns Huffman codec for ranks of the mtf with given |handle|.
-  // Different mtfs can use different rank distributions.
-  // May return nullptr if the codec doesn't exist.
-  const HuffmanCodec<uint32_t>* GetMtfHuffmanCodec(uint64_t handle) const {
-    const auto it = mtf_huffman_codecs_.find(handle);
-    if (it == mtf_huffman_codecs_.end()) return nullptr;
-    return it->second.get();
-  }
-
-  // Promotes id in all move-to-front sequences if ids can be shared by multiple
-  // sequences.
-  void PromoteIfNeeded(uint32_t id) {
-    if (!model_->AnyDescriptorHasCodingScheme() &&
-        model_->id_fallback_strategy() ==
-            MarkvModel::IdFallbackStrategy::kShortDescriptor) {
-      // Move-to-front sequences do not share ids. Nothing to do.
-      return;
-    }
-    multi_mtf_.Promote(id);
-  }
-
-  spv_validator_options validator_options_ = nullptr;
-  const AssemblyGrammar grammar_;
-  MarkvHeader header_;
-
-  // MARK-V model, not owned.
-  const MarkvModel* model_ = nullptr;
-
-  // Current instruction, current operand and current operand index.
-  spv_parsed_instruction_t inst_;
-  spv_parsed_operand_t operand_;
-  uint32_t operand_index_;
-
-  // Maps a result ID to its type ID.  By convention:
-  //  - a result ID that is a type definition maps to itself.
-  //  - a result ID without a type maps to 0.  (E.g. for OpLabel)
-  std::unordered_map<uint32_t, uint32_t> id_to_type_id_;
-
-  // Container for all move-to-front sequences.
-  MultiMoveToFront multi_mtf_;
-
-  // Id of the current function or zero if outside of function.
-  uint32_t cur_function_id_ = 0;
-
-  // Return type of the current function.
-  uint32_t cur_function_return_type_ = 0;
-
-  // Remaining function parameter types. This container is filled on OpFunction,
-  // and drained on OpFunctionParameter.
-  std::list<uint32_t> remaining_function_parameter_types_;
-
-  // List of ids local to the current function.
-  std::vector<uint32_t> ids_local_to_cur_function_;
-
-  // List of instructions in the order they are given in the module.
-  std::vector<std::unique_ptr<const val::Instruction>> instructions_;
-
-  // Container/computer for long (32-bit) id descriptors.
-  IdDescriptorCollection long_id_descriptors_;
-
-  // Container/computer for short id descriptors.
-  // Short descriptors are stored in uint32_t, but their actual bit width is
-  // defined with kShortDescriptorNumBits.
-  // It doesn't seem logical to have a different computer for short id
-  // descriptors, since one could actually map/truncate long descriptors.
-  // But as short descriptors have collisions, the efficiency of
-  // compression depends on the collision pattern, and short descriptors
-  // produced by function ShortHashU32Array have been empirically proven to
-  // produce better results.
-  IdDescriptorCollection short_id_descriptors_;
-
-  // Huffman codecs for move-to-front ranks. The map key is mtf handle. Doesn't
-  // need to contain a different codec for every handle as most use one and the
-  // same.
-  std::map<uint64_t, std::unique_ptr<HuffmanCodec<uint32_t>>>
-      mtf_huffman_codecs_;
-
-  // If not nullptr, codec will log comments on the compression process.
-  std::unique_ptr<MarkvLogger> logger_;
-
-  spv_const_context context_ = nullptr;
-
- private:
-  // Maps result id to the instruction which defined it.
-  std::unordered_map<uint32_t, const val::Instruction*> id_to_def_instruction_;
-
-  uint32_t id_bound_ = 1;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_CODEC_H_
diff --git a/source/comp/markv_decoder.cpp b/source/comp/markv_decoder.cpp
deleted file mode 100644
index 2211583..0000000
--- a/source/comp/markv_decoder.cpp
+++ /dev/null
@@ -1,925 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/markv_decoder.h"
-
-#include <cstring>
-#include <iterator>
-#include <numeric>
-
-#include "source/ext_inst.h"
-#include "source/opcode.h"
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-namespace comp {
-
-spv_result_t MarkvDecoder::DecodeNonIdWord(uint32_t* word) {
-  auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_);
-
-  if (codec) {
-    uint64_t decoded_value = 0;
-    if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
-      return Diag(SPV_ERROR_INVALID_BINARY)
-             << "Failed to decode non-id word with Huffman";
-
-    if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) {
-      // The word decoded successfully.
-      *word = uint32_t(decoded_value);
-      assert(*word == decoded_value);
-      return SPV_SUCCESS;
-    }
-
-    // Received kMarkvNoneOfTheAbove signal, use fallback decoding.
-  }
-
-  const size_t chunk_length =
-      model_->GetOperandVariableWidthChunkLength(operand_.type);
-  if (chunk_length) {
-    if (!reader_.ReadVariableWidthU32(word, chunk_length))
-      return Diag(SPV_ERROR_INVALID_BINARY)
-             << "Failed to decode non-id word with varint";
-  } else {
-    if (!reader_.ReadUnencoded(word))
-      return Diag(SPV_ERROR_INVALID_BINARY)
-             << "Failed to read unencoded non-id word";
-  }
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeOpcodeAndNumberOfOperands(
-    uint32_t* opcode, uint32_t* num_operands) {
-  // First try to use the Markov chain codec.
-  auto* codec =
-      model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode());
-  if (codec) {
-    uint64_t decoded_value = 0;
-    if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to decode opcode_and_num_operands, previous opcode is "
-             << spvOpcodeString(GetPrevOpcode());
-
-    if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) {
-      // The word was successfully decoded.
-      *opcode = uint32_t(decoded_value & 0xFFFF);
-      *num_operands = uint32_t(decoded_value >> 16);
-      return SPV_SUCCESS;
-    }
-
-    // Received kMarkvNoneOfTheAbove signal, use fallback decoding.
-  }
-
-  // Fallback to base-rate codec.
-  codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop);
-  assert(codec);
-  uint64_t decoded_value = 0;
-  if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
-    return Diag(SPV_ERROR_INTERNAL)
-           << "Failed to decode opcode_and_num_operands with global codec";
-
-  if (decoded_value == MarkvModel::GetMarkvNoneOfTheAbove()) {
-    // Received kMarkvNoneOfTheAbove signal, fallback further.
-    return SPV_UNSUPPORTED;
-  }
-
-  *opcode = uint32_t(decoded_value & 0xFFFF);
-  *num_operands = uint32_t(decoded_value >> 16);
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeMtfRankHuffman(uint64_t mtf,
-                                                uint32_t fallback_method,
-                                                uint32_t* rank) {
-  const auto* codec = GetMtfHuffmanCodec(mtf);
-  if (!codec) {
-    assert(fallback_method != kMtfNone);
-    codec = GetMtfHuffmanCodec(fallback_method);
-  }
-
-  if (!codec) return Diag(SPV_ERROR_INTERNAL) << "No codec to decode MTF rank";
-
-  uint32_t decoded_value = 0;
-  if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
-    return Diag(SPV_ERROR_INTERNAL) << "Failed to decode MTF rank with Huffman";
-
-  if (decoded_value == kMtfRankEncodedByValueSignal) {
-    // Decode by value.
-    if (!reader_.ReadVariableWidthU32(rank, model_->mtf_rank_chunk_length()))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to decode MTF rank with varint";
-    *rank += MarkvCodec::kMtfSmallestRankEncodedByValue;
-  } else {
-    // Decode using Huffman coding.
-    assert(decoded_value < MarkvCodec::kMtfSmallestRankEncodedByValue);
-    *rank = decoded_value;
-  }
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeIdWithDescriptor(uint32_t* id) {
-  auto* codec =
-      model_->GetIdDescriptorHuffmanCodec(inst_.opcode, operand_index_);
-
-  uint64_t mtf = kMtfNone;
-  if (codec) {
-    uint64_t decoded_value = 0;
-    if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to decode descriptor with Huffman";
-
-    if (decoded_value != MarkvModel::GetMarkvNoneOfTheAbove()) {
-      const uint32_t long_descriptor = uint32_t(decoded_value);
-      mtf = GetMtfLongIdDescriptor(long_descriptor);
-    }
-  }
-
-  if (mtf == kMtfNone) {
-    if (model_->id_fallback_strategy() !=
-        MarkvModel::IdFallbackStrategy::kShortDescriptor) {
-      return SPV_UNSUPPORTED;
-    }
-
-    uint64_t decoded_value = 0;
-    if (!reader_.ReadBits(&decoded_value, MarkvCodec::kShortDescriptorNumBits))
-      return Diag(SPV_ERROR_INTERNAL) << "Failed to read short descriptor";
-    const uint32_t short_descriptor = uint32_t(decoded_value);
-    if (short_descriptor == 0) {
-      // Forward declared id.
-      return SPV_UNSUPPORTED;
-    }
-    mtf = GetMtfShortIdDescriptor(short_descriptor);
-  }
-
-  return DecodeExistingId(mtf, id);
-}
-
-spv_result_t MarkvDecoder::DecodeExistingId(uint64_t mtf, uint32_t* id) {
-  assert(multi_mtf_.GetSize(mtf) > 0);
-  *id = 0;
-
-  uint32_t rank = 0;
-
-  if (multi_mtf_.GetSize(mtf) == 1) {
-    rank = 1;
-  } else {
-    const spv_result_t result =
-        DecodeMtfRankHuffman(mtf, kMtfGenericNonZeroRank, &rank);
-    if (result != SPV_SUCCESS) return result;
-  }
-
-  assert(rank);
-  if (!multi_mtf_.ValueFromRank(mtf, rank, id))
-    return Diag(SPV_ERROR_INTERNAL) << "MTF rank is out of bounds";
-
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeRefId(uint32_t* id) {
-  {
-    const spv_result_t result = DecodeIdWithDescriptor(id);
-    if (result != SPV_UNSUPPORTED) return result;
-  }
-
-  const bool can_forward_declare = spvOperandCanBeForwardDeclaredFunction(
-      SpvOp(inst_.opcode))(operand_index_);
-  uint32_t rank = 0;
-  *id = 0;
-
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kRuleBased) {
-    uint64_t mtf = GetRuleBasedMtf();
-    if (mtf != kMtfNone && !can_forward_declare) {
-      return DecodeExistingId(mtf, id);
-    }
-
-    if (mtf == kMtfNone) mtf = kMtfAll;
-    {
-      const spv_result_t result = DecodeMtfRankHuffman(mtf, kMtfAll, &rank);
-      if (result != SPV_SUCCESS) return result;
-    }
-
-    if (rank == 0) {
-      // This is the first occurrence of a forward declared id.
-      *id = GetIdBound();
-      SetIdBound(*id + 1);
-      multi_mtf_.Insert(kMtfAll, *id);
-      multi_mtf_.Insert(kMtfForwardDeclared, *id);
-      if (mtf != kMtfAll) multi_mtf_.Insert(mtf, *id);
-    } else {
-      if (!multi_mtf_.ValueFromRank(mtf, rank, id))
-        return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds";
-    }
-  } else {
-    assert(can_forward_declare);
-
-    if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length()))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to decode MTF rank with varint";
-
-    if (rank == 0) {
-      // This is the first occurrence of a forward declared id.
-      *id = GetIdBound();
-      SetIdBound(*id + 1);
-      multi_mtf_.Insert(kMtfForwardDeclared, *id);
-    } else {
-      if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank, id))
-        return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds";
-    }
-  }
-  assert(*id);
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeTypeId() {
-  if (inst_.opcode == SpvOpFunctionParameter) {
-    assert(!remaining_function_parameter_types_.empty());
-    inst_.type_id = remaining_function_parameter_types_.front();
-    remaining_function_parameter_types_.pop_front();
-    return SPV_SUCCESS;
-  }
-
-  {
-    const spv_result_t result = DecodeIdWithDescriptor(&inst_.type_id);
-    if (result != SPV_UNSUPPORTED) return result;
-  }
-
-  assert(model_->id_fallback_strategy() ==
-         MarkvModel::IdFallbackStrategy::kRuleBased);
-
-  uint64_t mtf = GetRuleBasedMtf();
-  assert(!spvOperandCanBeForwardDeclaredFunction(SpvOp(inst_.opcode))(
-      operand_index_));
-
-  if (mtf == kMtfNone) {
-    mtf = kMtfTypeNonFunction;
-    // Function types should have been handled by GetRuleBasedMtf.
-    assert(inst_.opcode != SpvOpFunction);
-  }
-
-  return DecodeExistingId(mtf, &inst_.type_id);
-}
-
-spv_result_t MarkvDecoder::DecodeResultId() {
-  uint32_t rank = 0;
-
-  const uint64_t num_still_forward_declared =
-      multi_mtf_.GetSize(kMtfForwardDeclared);
-
-  if (num_still_forward_declared) {
-    // Some ids were forward declared. Check if this id is one of them.
-    uint64_t id_was_forward_declared;
-    if (!reader_.ReadBits(&id_was_forward_declared, 1))
-      return Diag(SPV_ERROR_INVALID_BINARY)
-             << "Failed to read id_was_forward_declared flag";
-
-    if (id_was_forward_declared) {
-      if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length()))
-        return Diag(SPV_ERROR_INVALID_BINARY)
-               << "Failed to read MTF rank of forward declared id";
-
-      if (rank) {
-        // The id was forward declared, recover it from kMtfForwardDeclared.
-        if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank,
-                                      &inst_.result_id))
-          return Diag(SPV_ERROR_INTERNAL)
-                 << "Forward declared MTF rank is out of bounds";
-
-        // We can now remove the id from kMtfForwardDeclared.
-        if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id))
-          return Diag(SPV_ERROR_INTERNAL)
-                 << "Failed to remove id from kMtfForwardDeclared";
-      }
-    }
-  }
-
-  if (inst_.result_id == 0) {
-    // The id was not forward declared, issue a new id.
-    inst_.result_id = GetIdBound();
-    SetIdBound(inst_.result_id + 1);
-  }
-
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kRuleBased) {
-    if (!rank) {
-      multi_mtf_.Insert(kMtfAll, inst_.result_id);
-    }
-  }
-
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeLiteralNumber(
-    const spv_parsed_operand_t& operand) {
-  if (operand.number_bit_width <= 32) {
-    uint32_t word = 0;
-    const spv_result_t result = DecodeNonIdWord(&word);
-    if (result != SPV_SUCCESS) return result;
-    inst_words_.push_back(word);
-  } else {
-    assert(operand.number_bit_width <= 64);
-    uint64_t word = 0;
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      if (!reader_.ReadVariableWidthU64(&word, model_->u64_chunk_length()))
-        return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal U64";
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int64_t val = 0;
-      if (!reader_.ReadVariableWidthS64(&val, model_->s64_chunk_length(),
-                                        model_->s64_block_exponent()))
-        return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal S64";
-      std::memcpy(&word, &val, 8);
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      if (!reader_.ReadUnencoded(&word))
-        return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal F64";
-    } else {
-      return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length";
-    }
-    inst_words_.push_back(static_cast<uint32_t>(word));
-    inst_words_.push_back(static_cast<uint32_t>(word >> 32));
-  }
-  return SPV_SUCCESS;
-}
-
-bool MarkvDecoder::ReadToByteBreak(size_t byte_break_if_less_than) {
-  const size_t num_bits_to_next_byte =
-      GetNumBitsToNextByte(reader_.GetNumReadBits());
-  if (num_bits_to_next_byte == 0 ||
-      num_bits_to_next_byte > byte_break_if_less_than)
-    return true;
-
-  uint64_t bits = 0;
-  if (!reader_.ReadBits(&bits, num_bits_to_next_byte)) return false;
-
-  assert(bits == 0);
-  if (bits != 0) return false;
-
-  return true;
-}
-
-spv_result_t MarkvDecoder::DecodeModule(std::vector<uint32_t>* spirv_binary) {
-  const bool header_read_success =
-      reader_.ReadUnencoded(&header_.magic_number) &&
-      reader_.ReadUnencoded(&header_.markv_version) &&
-      reader_.ReadUnencoded(&header_.markv_model) &&
-      reader_.ReadUnencoded(&header_.markv_length_in_bits) &&
-      reader_.ReadUnencoded(&header_.spirv_version) &&
-      reader_.ReadUnencoded(&header_.spirv_generator);
-
-  if (!header_read_success)
-    return Diag(SPV_ERROR_INVALID_BINARY) << "Unable to read MARK-V header";
-
-  if (header_.markv_length_in_bits == 0)
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "Header markv_length_in_bits field is zero";
-
-  if (header_.magic_number != MarkvCodec::kMarkvMagicNumber)
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "MARK-V binary has incorrect magic number";
-
-  // TODO(atgoo@github.com): Print version strings.
-  if (header_.markv_version != MarkvCodec::GetMarkvVersion())
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "MARK-V binary and the codec have different versions";
-
-  const uint32_t model_type = header_.markv_model >> 16;
-  const uint32_t model_version = header_.markv_model & 0xFFFF;
-  if (model_type != model_->model_type())
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "MARK-V binary and the codec use different MARK-V models";
-
-  if (model_version != model_->model_version())
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "MARK-V binary and the codec use different versions if the same "
-           << "MARK-V model";
-
-  spirv_.reserve(header_.markv_length_in_bits / 2);  // Heuristic.
-  spirv_.resize(5, 0);
-  spirv_[0] = SpvMagicNumber;
-  spirv_[1] = header_.spirv_version;
-  spirv_[2] = header_.spirv_generator;
-
-  if (logger_) {
-    reader_.SetCallback(
-        [this](const std::string& str) { logger_->AppendBitSequence(str); });
-  }
-
-  while (reader_.GetNumReadBits() < header_.markv_length_in_bits) {
-    inst_ = {};
-    const spv_result_t decode_result = DecodeInstruction();
-    if (decode_result != SPV_SUCCESS) return decode_result;
-  }
-
-  if (validator_options_) {
-    spv_const_binary_t validation_binary = {spirv_.data(), spirv_.size()};
-    const spv_result_t result = spvValidateWithOptions(
-        context_, validator_options_, &validation_binary, nullptr);
-    if (result != SPV_SUCCESS) return result;
-  }
-
-  // Validate the decode binary
-  if (reader_.GetNumReadBits() != header_.markv_length_in_bits ||
-      !reader_.OnlyZeroesLeft()) {
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "MARK-V binary has wrong stated bit length "
-           << reader_.GetNumReadBits() << " " << header_.markv_length_in_bits;
-  }
-
-  // Decoding of the module is finished, validation state should have correct
-  // id bound.
-  spirv_[3] = GetIdBound();
-
-  *spirv_binary = std::move(spirv_);
-  return SPV_SUCCESS;
-}
-
-// TODO(atgoo@github.com): The implementation borrows heavily from
-// Parser::parseOperand.
-// Consider coupling them together in some way once MARK-V codec is more mature.
-// For now it's better to keep the code independent for experimentation
-// purposes.
-spv_result_t MarkvDecoder::DecodeOperand(
-    size_t operand_offset, const spv_operand_type_t type,
-    spv_operand_pattern_t* expected_operands) {
-  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
-
-  memset(&operand_, 0, sizeof(operand_));
-
-  assert((operand_offset >> 16) == 0);
-  operand_.offset = static_cast<uint16_t>(operand_offset);
-  operand_.type = type;
-
-  // Set default values, may be updated later.
-  operand_.number_kind = SPV_NUMBER_NONE;
-  operand_.number_bit_width = 0;
-
-  const size_t first_word_index = inst_words_.size();
-
-  switch (type) {
-    case SPV_OPERAND_TYPE_RESULT_ID: {
-      const spv_result_t result = DecodeResultId();
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(inst_.result_id);
-      SetIdBound(std::max(GetIdBound(), inst_.result_id + 1));
-      PromoteIfNeeded(inst_.result_id);
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_TYPE_ID: {
-      const spv_result_t result = DecodeTypeId();
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(inst_.type_id);
-      SetIdBound(std::max(GetIdBound(), inst_.type_id + 1));
-      PromoteIfNeeded(inst_.type_id);
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_ID:
-    case SPV_OPERAND_TYPE_OPTIONAL_ID:
-    case SPV_OPERAND_TYPE_SCOPE_ID:
-    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: {
-      uint32_t id = 0;
-      const spv_result_t result = DecodeRefId(&id);
-      if (result != SPV_SUCCESS) return result;
-
-      if (id == 0) return Diag(SPV_ERROR_INVALID_BINARY) << "Decoded id is 0";
-
-      if (type == SPV_OPERAND_TYPE_ID || type == SPV_OPERAND_TYPE_OPTIONAL_ID) {
-        operand_.type = SPV_OPERAND_TYPE_ID;
-
-        if (opcode == SpvOpExtInst && operand_.offset == 3) {
-          // The current word is the extended instruction set id.
-          // Set the extended instruction set type for the current
-          // instruction.
-          auto ext_inst_type_iter = import_id_to_ext_inst_type_.find(id);
-          if (ext_inst_type_iter == import_id_to_ext_inst_type_.end()) {
-            return Diag(SPV_ERROR_INVALID_ID)
-                   << "OpExtInst set id " << id
-                   << " does not reference an OpExtInstImport result Id";
-          }
-          inst_.ext_inst_type = ext_inst_type_iter->second;
-        }
-      }
-
-      inst_words_.push_back(id);
-      SetIdBound(std::max(GetIdBound(), id + 1));
-      PromoteIfNeeded(id);
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
-      uint32_t word = 0;
-      const spv_result_t result = DecodeNonIdWord(&word);
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(word);
-
-      assert(SpvOpExtInst == opcode);
-      assert(inst_.ext_inst_type != SPV_EXT_INST_TYPE_NONE);
-      spv_ext_inst_desc ext_inst;
-      if (grammar_.lookupExtInst(inst_.ext_inst_type, word, &ext_inst))
-        return Diag(SPV_ERROR_INVALID_BINARY)
-               << "Invalid extended instruction number: " << word;
-      spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
-    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: {
-      // These are regular single-word literal integer operands.
-      // Post-parsing validation should check the range of the parsed value.
-      operand_.type = SPV_OPERAND_TYPE_LITERAL_INTEGER;
-      // It turns out they are always unsigned integers!
-      operand_.number_kind = SPV_NUMBER_UNSIGNED_INT;
-      operand_.number_bit_width = 32;
-
-      uint32_t word = 0;
-      const spv_result_t result = DecodeNonIdWord(&word);
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(word);
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
-    case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: {
-      operand_.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER;
-      if (opcode == SpvOpSwitch) {
-        // The literal operands have the same type as the value
-        // referenced by the selector Id.
-        const uint32_t selector_id = inst_words_.at(1);
-        const auto type_id_iter = id_to_type_id_.find(selector_id);
-        if (type_id_iter == id_to_type_id_.end() || type_id_iter->second == 0) {
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Invalid OpSwitch: selector id " << selector_id
-                 << " has no type";
-        }
-        uint32_t type_id = type_id_iter->second;
-
-        if (selector_id == type_id) {
-          // Recall that by convention, a result ID that is a type definition
-          // maps to itself.
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Invalid OpSwitch: selector id " << selector_id
-                 << " is a type, not a value";
-        }
-        if (auto error = SetNumericTypeInfoForType(&operand_, type_id))
-          return error;
-        if (operand_.number_kind != SPV_NUMBER_UNSIGNED_INT &&
-            operand_.number_kind != SPV_NUMBER_SIGNED_INT) {
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Invalid OpSwitch: selector id " << selector_id
-                 << " is not a scalar integer";
-        }
-      } else {
-        assert(opcode == SpvOpConstant || opcode == SpvOpSpecConstant);
-        // The literal number type is determined by the type Id for the
-        // constant.
-        assert(inst_.type_id);
-        if (auto error = SetNumericTypeInfoForType(&operand_, inst_.type_id))
-          return error;
-      }
-
-      if (auto error = DecodeLiteralNumber(operand_)) return error;
-
-      break;
-    }
-
-    case SPV_OPERAND_TYPE_LITERAL_STRING:
-    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: {
-      operand_.type = SPV_OPERAND_TYPE_LITERAL_STRING;
-      std::vector<char> str;
-      auto* codec = model_->GetLiteralStringHuffmanCodec(inst_.opcode);
-
-      if (codec) {
-        std::string decoded_string;
-        const bool huffman_result =
-            codec->DecodeFromStream(GetReadBitCallback(), &decoded_string);
-        assert(huffman_result);
-        if (!huffman_result)
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Failed to read literal string";
-
-        if (decoded_string != "kMarkvNoneOfTheAbove") {
-          std::copy(decoded_string.begin(), decoded_string.end(),
-                    std::back_inserter(str));
-          str.push_back('\0');
-        }
-      }
-
-      // The loop is expected to terminate once we encounter '\0' or exhaust
-      // the bit stream.
-      if (str.empty()) {
-        while (true) {
-          char ch = 0;
-          if (!reader_.ReadUnencoded(&ch))
-            return Diag(SPV_ERROR_INVALID_BINARY)
-                   << "Failed to read literal string";
-
-          str.push_back(ch);
-
-          if (ch == '\0') break;
-        }
-      }
-
-      while (str.size() % 4 != 0) str.push_back('\0');
-
-      inst_words_.resize(inst_words_.size() + str.size() / 4);
-      std::memcpy(&inst_words_[first_word_index], str.data(), str.size());
-
-      if (SpvOpExtInstImport == opcode) {
-        // Record the extended instruction type for the ID for this import.
-        // 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(str.data());
-        if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) {
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Invalid extended instruction import '" << str.data()
-                 << "'";
-        }
-        // We must have parsed a valid result ID.  It's a condition
-        // of the grammar, and we only accept non-zero result Ids.
-        assert(inst_.result_id);
-        const bool inserted =
-            import_id_to_ext_inst_type_.emplace(inst_.result_id, ext_inst_type)
-                .second;
-        (void)inserted;
-        assert(inserted);
-      }
-      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_OPTIONAL_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: {
-      // A single word that is a plain enum value.
-      uint32_t word = 0;
-      const spv_result_t result = DecodeNonIdWord(&word);
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(word);
-
-      // Map an optional operand type to its corresponding concrete type.
-      if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER)
-        operand_.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER;
-
-      spv_operand_desc entry;
-      if (grammar_.lookupOperand(type, word, &entry)) {
-        return Diag(SPV_ERROR_INVALID_BINARY)
-               << "Invalid " << spvOperandTypeStr(operand_.type)
-               << " operand: " << word;
-      }
-
-      // Prepare to accept operands to this operand, if needed.
-      spvPushOperandTypes(entry->operandTypes, expected_operands);
-      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_OPTIONAL_IMAGE:
-    case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS:
-    case SPV_OPERAND_TYPE_SELECTION_CONTROL: {
-      // This operand is a mask.
-      uint32_t word = 0;
-      const spv_result_t result = DecodeNonIdWord(&word);
-      if (result != SPV_SUCCESS) return result;
-
-      inst_words_.push_back(word);
-
-      // Map an optional operand type to its corresponding concrete type.
-      if (type == SPV_OPERAND_TYPE_OPTIONAL_IMAGE)
-        operand_.type = SPV_OPERAND_TYPE_IMAGE;
-      else if (type == SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS)
-        operand_.type = SPV_OPERAND_TYPE_MEMORY_ACCESS;
-
-      // Check validity of set mask bits. Also prepare for operands for those
-      // masks if they have any.  To get operand order correct, scan from
-      // MSB to LSB since we can only prepend operands to a pattern.
-      // The only case in the grammar where you have more than one mask bit
-      // having an operand is for image operands.  See SPIR-V 3.14 Image
-      // Operands.
-      uint32_t remaining_word = word;
-      for (uint32_t mask = (1u << 31); remaining_word; mask >>= 1) {
-        if (remaining_word & mask) {
-          spv_operand_desc entry;
-          if (grammar_.lookupOperand(type, mask, &entry)) {
-            return Diag(SPV_ERROR_INVALID_BINARY)
-                   << "Invalid " << spvOperandTypeStr(operand_.type)
-                   << " operand: " << word << " has invalid mask component "
-                   << mask;
-          }
-          remaining_word ^= mask;
-          spvPushOperandTypes(entry->operandTypes, expected_operands);
-        }
-      }
-      if (word == 0) {
-        // An all-zeroes mask *might* also be valid.
-        spv_operand_desc entry;
-        if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) {
-          // Prepare for its operands, if any.
-          spvPushOperandTypes(entry->operandTypes, expected_operands);
-        }
-      }
-      break;
-    }
-    default:
-      return Diag(SPV_ERROR_INVALID_BINARY)
-             << "Internal error: Unhandled operand type: " << type;
-  }
-
-  operand_.num_words = uint16_t(inst_words_.size() - first_word_index);
-
-  assert(spvOperandIsConcrete(operand_.type));
-
-  parsed_operands_.push_back(operand_);
-
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::DecodeInstruction() {
-  parsed_operands_.clear();
-  inst_words_.clear();
-
-  // Opcode/num_words placeholder, the word will be filled in later.
-  inst_words_.push_back(0);
-
-  bool num_operands_still_unknown = true;
-  {
-    uint32_t opcode = 0;
-    uint32_t num_operands = 0;
-
-    const spv_result_t opcode_decoding_result =
-        DecodeOpcodeAndNumberOfOperands(&opcode, &num_operands);
-    if (opcode_decoding_result < 0) return opcode_decoding_result;
-
-    if (opcode_decoding_result == SPV_SUCCESS) {
-      inst_.num_operands = static_cast<uint16_t>(num_operands);
-      num_operands_still_unknown = false;
-    } else {
-      if (!reader_.ReadVariableWidthU32(&opcode,
-                                        model_->opcode_chunk_length())) {
-        return Diag(SPV_ERROR_INVALID_BINARY)
-               << "Failed to read opcode of instruction";
-      }
-    }
-
-    inst_.opcode = static_cast<uint16_t>(opcode);
-  }
-
-  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
-
-  spv_opcode_desc opcode_desc;
-  if (grammar_.lookupOpcode(opcode, &opcode_desc) != SPV_SUCCESS) {
-    return Diag(SPV_ERROR_INVALID_BINARY) << "Invalid opcode";
-  }
-
-  spv_operand_pattern_t expected_operands;
-  expected_operands.reserve(opcode_desc->numTypes);
-  for (auto i = 0; i < opcode_desc->numTypes; i++) {
-    expected_operands.push_back(
-        opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]);
-  }
-
-  if (num_operands_still_unknown) {
-    if (!OpcodeHasFixedNumberOfOperands(opcode)) {
-      if (!reader_.ReadVariableWidthU16(&inst_.num_operands,
-                                        model_->num_operands_chunk_length()))
-        return Diag(SPV_ERROR_INVALID_BINARY)
-               << "Failed to read num_operands of instruction";
-    } else {
-      inst_.num_operands = static_cast<uint16_t>(expected_operands.size());
-    }
-  }
-
-  for (operand_index_ = 0;
-       operand_index_ < static_cast<size_t>(inst_.num_operands);
-       ++operand_index_) {
-    assert(!expected_operands.empty());
-    const spv_operand_type_t type =
-        spvTakeFirstMatchableOperand(&expected_operands);
-
-    const size_t operand_offset = inst_words_.size();
-
-    const spv_result_t decode_result =
-        DecodeOperand(operand_offset, type, &expected_operands);
-
-    if (decode_result != SPV_SUCCESS) return decode_result;
-  }
-
-  assert(inst_.num_operands == parsed_operands_.size());
-
-  // Only valid while inst_words_ and parsed_operands_ remain unchanged (until
-  // next DecodeInstruction call).
-  inst_.words = inst_words_.data();
-  inst_.operands = parsed_operands_.empty() ? nullptr : parsed_operands_.data();
-  inst_.num_words = static_cast<uint16_t>(inst_words_.size());
-  inst_words_[0] = spvOpcodeMake(inst_.num_words, SpvOp(inst_.opcode));
-
-  std::copy(inst_words_.begin(), inst_words_.end(), std::back_inserter(spirv_));
-
-  assert(inst_.num_words ==
-             std::accumulate(
-                 parsed_operands_.begin(), parsed_operands_.end(), 1,
-                 [](int num_words, const spv_parsed_operand_t& operand) {
-                   return num_words += operand.num_words;
-                 }) &&
-         "num_words in instruction doesn't correspond to the sum of num_words"
-         "in the operands");
-
-  RecordNumberType();
-  ProcessCurInstruction();
-
-  if (!ReadToByteBreak(MarkvCodec::kByteBreakAfterInstIfLessThanUntilNextByte))
-    return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read to byte break";
-
-  if (logger_) {
-    logger_->NewLine();
-    std::stringstream ss;
-    ss << spvOpcodeString(opcode) << " ";
-    for (size_t index = 1; index < inst_words_.size(); ++index)
-      ss << inst_words_[index] << " ";
-    logger_->AppendText(ss.str());
-    logger_->NewLine();
-    logger_->NewLine();
-    if (!logger_->DebugInstruction(inst_)) return SPV_REQUESTED_TERMINATION;
-  }
-
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvDecoder::SetNumericTypeInfoForType(
-    spv_parsed_operand_t* parsed_operand, uint32_t type_id) {
-  assert(type_id != 0);
-  auto type_info_iter = type_id_to_number_type_info_.find(type_id);
-  if (type_info_iter == type_id_to_number_type_info_.end()) {
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "Type Id " << type_id << " is not a type";
-  }
-
-  const NumberType& info = type_info_iter->second;
-  if (info.type == SPV_NUMBER_NONE) {
-    // This is a valid type, but for something other than a scalar number.
-    return Diag(SPV_ERROR_INVALID_BINARY)
-           << "Type Id " << type_id << " is not a scalar numeric type";
-  }
-
-  parsed_operand->number_kind = info.type;
-  parsed_operand->number_bit_width = info.bit_width;
-  // Round up the word count.
-  parsed_operand->num_words = static_cast<uint16_t>((info.bit_width + 31) / 32);
-  return SPV_SUCCESS;
-}
-
-void MarkvDecoder::RecordNumberType() {
-  const SpvOp opcode = static_cast<SpvOp>(inst_.opcode);
-  if (spvOpcodeGeneratesType(opcode)) {
-    NumberType info = {SPV_NUMBER_NONE, 0};
-    if (SpvOpTypeInt == opcode) {
-      info.bit_width = inst_.words[inst_.operands[1].offset];
-      info.type = inst_.words[inst_.operands[2].offset]
-                      ? SPV_NUMBER_SIGNED_INT
-                      : SPV_NUMBER_UNSIGNED_INT;
-    } else if (SpvOpTypeFloat == opcode) {
-      info.bit_width = inst_.words[inst_.operands[1].offset];
-      info.type = SPV_NUMBER_FLOATING;
-    }
-    // The *result* Id of a type generating instruction is the type Id.
-    type_id_to_number_type_info_[inst_.result_id] = info;
-  }
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/markv_decoder.h b/source/comp/markv_decoder.h
deleted file mode 100644
index 4d8402b..0000000
--- a/source/comp/markv_decoder.h
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/bit_stream.h"
-#include "source/comp/markv.h"
-#include "source/comp/markv_codec.h"
-#include "source/comp/markv_logger.h"
-#include "source/util/make_unique.h"
-
-#ifndef SOURCE_COMP_MARKV_DECODER_H_
-#define SOURCE_COMP_MARKV_DECODER_H_
-
-namespace spvtools {
-namespace comp {
-
-class MarkvLogger;
-
-// Decodes MARK-V buffers written by MarkvEncoder.
-class MarkvDecoder : public MarkvCodec {
- public:
-  // |model| is owned by the caller, must be not null and valid during the
-  // lifetime of MarkvEncoder.
-  MarkvDecoder(spv_const_context context, const std::vector<uint8_t>& markv,
-               const MarkvCodecOptions& options, const MarkvModel* model)
-      : MarkvCodec(context, GetValidatorOptions(options), model),
-        options_(options),
-        reader_(markv) {
-    SetIdBound(1);
-    parsed_operands_.reserve(25);
-    inst_words_.reserve(25);
-  }
-  ~MarkvDecoder() = default;
-
-  // Creates an internal logger which writes comments on the decoding process.
-  void CreateLogger(MarkvLogConsumer log_consumer,
-                    MarkvDebugConsumer debug_consumer) {
-    logger_ = MakeUnique<MarkvLogger>(log_consumer, debug_consumer);
-  }
-
-  // Decodes SPIR-V from MARK-V and stores the words in |spirv_binary|.
-  // Can be called only once. Fails if data of wrong format or ends prematurely,
-  // of if validation fails.
-  spv_result_t DecodeModule(std::vector<uint32_t>* spirv_binary);
-
-  // Creates and returns validator options. Returned value owned by the caller.
-  static spv_validator_options GetValidatorOptions(
-      const MarkvCodecOptions& options) {
-    return options.validate_spirv_binary ? spvValidatorOptionsCreate()
-                                         : nullptr;
-  }
-
- private:
-  // Describes the format of a typed literal number.
-  struct NumberType {
-    spv_number_kind_t type;
-    uint32_t bit_width;
-  };
-
-  // Reads a single bit from reader_. The read bit is stored in |bit|.
-  // Returns false iff reader_ fails.
-  bool ReadBit(bool* bit) {
-    uint64_t bits = 0;
-    const bool result = reader_.ReadBits(&bits, 1);
-    if (result) *bit = bits ? true : false;
-    return result;
-  };
-
-  // Returns ReadBit bound to the class object.
-  std::function<bool(bool*)> GetReadBitCallback() {
-    return std::bind(&MarkvDecoder::ReadBit, this, std::placeholders::_1);
-  }
-
-  // Reads a single non-id word from bit stream. operand_.type determines if
-  // the word needs to be decoded and how.
-  spv_result_t DecodeNonIdWord(uint32_t* word);
-
-  // Reads and decodes both opcode and num_operands as a single code.
-  // Returns SPV_UNSUPPORTED iff no suitable codec was found.
-  spv_result_t DecodeOpcodeAndNumberOfOperands(uint32_t* opcode,
-                                               uint32_t* num_operands);
-
-  // Reads mtf rank from bit stream. |mtf| is used to determine the codec
-  // scheme. |fallback_method| is used if no codec defined for |mtf|.
-  spv_result_t DecodeMtfRankHuffman(uint64_t mtf, uint32_t fallback_method,
-                                    uint32_t* rank);
-
-  // Reads id using coding based on mtf associated with the id descriptor.
-  // Returns SPV_UNSUPPORTED iff fallback method needs to be used.
-  spv_result_t DecodeIdWithDescriptor(uint32_t* id);
-
-  // Reads id using coding based on the given |mtf|, which is expected to
-  // contain the needed |id|.
-  spv_result_t DecodeExistingId(uint64_t mtf, uint32_t* id);
-
-  // Reads type id of the current instruction if can't be inferred.
-  spv_result_t DecodeTypeId();
-
-  // Reads result id of the current instruction if can't be inferred.
-  spv_result_t DecodeResultId();
-
-  // Reads id which is neither type nor result id.
-  spv_result_t DecodeRefId(uint32_t* id);
-
-  // Reads and discards bits until the beginning of the next byte if the
-  // number of bits until the next byte is less than |byte_break_if_less_than|.
-  bool ReadToByteBreak(size_t byte_break_if_less_than);
-
-  // Returns instruction words decoded up to this point.
-  const uint32_t* GetInstWords() const override { return inst_words_.data(); }
-
-  // Reads a literal number as it is described in |operand| from the bit stream,
-  // decodes and writes it to spirv_.
-  spv_result_t DecodeLiteralNumber(const spv_parsed_operand_t& operand);
-
-  // Reads instruction from bit stream, decodes and validates it.
-  // Decoded instruction is valid until the next call of DecodeInstruction().
-  spv_result_t DecodeInstruction();
-
-  // Read operand from the stream decodes and validates it.
-  spv_result_t DecodeOperand(size_t operand_offset,
-                             const spv_operand_type_t type,
-                             spv_operand_pattern_t* expected_operands);
-
-  // Records the numeric type for an operand according to the type information
-  // associated with the given non-zero type Id.  This can fail if the type Id
-  // is not a type Id, or if the type Id does not reference a scalar numeric
-  // type.  On success, return SPV_SUCCESS and populates the num_words,
-  // number_kind, and number_bit_width fields of parsed_operand.
-  spv_result_t SetNumericTypeInfoForType(spv_parsed_operand_t* parsed_operand,
-                                         uint32_t type_id);
-
-  // Records the number type for the current instruction, if it generates a
-  // type. For types that aren't scalar numbers, record something with number
-  // kind SPV_NUMBER_NONE.
-  void RecordNumberType();
-
-  MarkvCodecOptions options_;
-
-  // Temporary sink where decoded SPIR-V words are written. Once it contains the
-  // entire module, the container is moved and returned.
-  std::vector<uint32_t> spirv_;
-
-  // Bit stream containing encoded data.
-  BitReaderWord64 reader_;
-
-  // Temporary storage for operands of the currently parsed instruction.
-  // Valid until next DecodeInstruction call.
-  std::vector<spv_parsed_operand_t> parsed_operands_;
-
-  // Temporary storage for current instruction words.
-  // Valid until next DecodeInstruction call.
-  std::vector<uint32_t> inst_words_;
-
-  // Maps a type ID to its number type description.
-  std::unordered_map<uint32_t, NumberType> type_id_to_number_type_info_;
-
-  // Maps an ExtInstImport id to the extended instruction type.
-  std::unordered_map<uint32_t, spv_ext_inst_type_t> import_id_to_ext_inst_type_;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_DECODER_H_
diff --git a/source/comp/markv_encoder.cpp b/source/comp/markv_encoder.cpp
deleted file mode 100644
index 1abd586..0000000
--- a/source/comp/markv_encoder.cpp
+++ /dev/null
@@ -1,486 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/markv_encoder.h"
-
-#include "source/binary.h"
-#include "source/opcode.h"
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-const size_t kCommentNumWhitespaces = 2;
-
-}  // namespace
-
-spv_result_t MarkvEncoder::EncodeNonIdWord(uint32_t word) {
-  auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_);
-
-  if (codec) {
-    uint64_t bits = 0;
-    size_t num_bits = 0;
-    if (codec->Encode(word, &bits, &num_bits)) {
-      // Encoding successful.
-      writer_.WriteBits(bits, num_bits);
-      return SPV_SUCCESS;
-    } else {
-      // Encoding failed, write kMarkvNoneOfTheAbove flag.
-      if (!codec->Encode(MarkvModel::GetMarkvNoneOfTheAbove(), &bits,
-                         &num_bits))
-        return Diag(SPV_ERROR_INTERNAL)
-               << "Non-id word Huffman table for "
-               << spvOpcodeString(SpvOp(inst_.opcode)) << " operand index "
-               << operand_index_ << " is missing kMarkvNoneOfTheAbove";
-      writer_.WriteBits(bits, num_bits);
-    }
-  }
-
-  // Fallback encoding.
-  const size_t chunk_length =
-      model_->GetOperandVariableWidthChunkLength(operand_.type);
-  if (chunk_length) {
-    writer_.WriteVariableWidthU32(word, chunk_length);
-  } else {
-    writer_.WriteUnencoded(word);
-  }
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvEncoder::EncodeOpcodeAndNumOperands(uint32_t opcode,
-                                                      uint32_t num_operands) {
-  uint64_t bits = 0;
-  size_t num_bits = 0;
-
-  const uint32_t word = opcode | (num_operands << 16);
-
-  // First try to use the Markov chain codec.
-  auto* codec =
-      model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode());
-  if (codec) {
-    if (codec->Encode(word, &bits, &num_bits)) {
-      // The word was successfully encoded into bits/num_bits.
-      writer_.WriteBits(bits, num_bits);
-      return SPV_SUCCESS;
-    } else {
-      // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove
-      // and use fallback encoding.
-      if (!codec->Encode(MarkvModel::GetMarkvNoneOfTheAbove(), &bits,
-                         &num_bits))
-        return Diag(SPV_ERROR_INTERNAL)
-               << "opcode_and_num_operands Huffman table for "
-               << spvOpcodeString(GetPrevOpcode())
-               << "is missing kMarkvNoneOfTheAbove";
-      writer_.WriteBits(bits, num_bits);
-    }
-  }
-
-  // Fallback to base-rate codec.
-  codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop);
-  assert(codec);
-  if (codec->Encode(word, &bits, &num_bits)) {
-    // The word was successfully encoded into bits/num_bits.
-    writer_.WriteBits(bits, num_bits);
-    return SPV_SUCCESS;
-  } else {
-    // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove
-    // and return false.
-    if (!codec->Encode(MarkvModel::GetMarkvNoneOfTheAbove(), &bits, &num_bits))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Global opcode_and_num_operands Huffman table is missing "
-             << "kMarkvNoneOfTheAbove";
-    writer_.WriteBits(bits, num_bits);
-    return SPV_UNSUPPORTED;
-  }
-}
-
-spv_result_t MarkvEncoder::EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf,
-                                                uint64_t fallback_method) {
-  const auto* codec = GetMtfHuffmanCodec(mtf);
-  if (!codec) {
-    assert(fallback_method != kMtfNone);
-    codec = GetMtfHuffmanCodec(fallback_method);
-  }
-
-  if (!codec) return Diag(SPV_ERROR_INTERNAL) << "No codec to encode MTF rank";
-
-  uint64_t bits = 0;
-  size_t num_bits = 0;
-  if (rank < MarkvCodec::kMtfSmallestRankEncodedByValue) {
-    // Encode using Huffman coding.
-    if (!codec->Encode(rank, &bits, &num_bits))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to encode MTF rank with Huffman";
-
-    writer_.WriteBits(bits, num_bits);
-  } else {
-    // Encode by value.
-    if (!codec->Encode(MarkvCodec::kMtfRankEncodedByValueSignal, &bits,
-                       &num_bits))
-      return Diag(SPV_ERROR_INTERNAL)
-             << "Failed to encode kMtfRankEncodedByValueSignal";
-
-    writer_.WriteBits(bits, num_bits);
-    writer_.WriteVariableWidthU32(
-        rank - MarkvCodec::kMtfSmallestRankEncodedByValue,
-        model_->mtf_rank_chunk_length());
-  }
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvEncoder::EncodeIdWithDescriptor(uint32_t id) {
-  // Get the descriptor for id.
-  const uint32_t long_descriptor = long_id_descriptors_.GetDescriptor(id);
-  auto* codec =
-      model_->GetIdDescriptorHuffmanCodec(inst_.opcode, operand_index_);
-  uint64_t bits = 0;
-  size_t num_bits = 0;
-  uint64_t mtf = kMtfNone;
-  if (long_descriptor && codec &&
-      codec->Encode(long_descriptor, &bits, &num_bits)) {
-    // If the descriptor exists and is in the table, write the descriptor and
-    // proceed to encoding the rank.
-    writer_.WriteBits(bits, num_bits);
-    mtf = GetMtfLongIdDescriptor(long_descriptor);
-  } else {
-    if (codec) {
-      // The descriptor doesn't exist or we have no coding for it. Write
-      // kMarkvNoneOfTheAbove and go to fallback method.
-      if (!codec->Encode(MarkvModel::GetMarkvNoneOfTheAbove(), &bits,
-                         &num_bits))
-        return Diag(SPV_ERROR_INTERNAL)
-               << "Descriptor Huffman table for "
-               << spvOpcodeString(SpvOp(inst_.opcode)) << " operand index "
-               << operand_index_ << " is missing kMarkvNoneOfTheAbove";
-
-      writer_.WriteBits(bits, num_bits);
-    }
-
-    if (model_->id_fallback_strategy() !=
-        MarkvModel::IdFallbackStrategy::kShortDescriptor) {
-      return SPV_UNSUPPORTED;
-    }
-
-    const uint32_t short_descriptor = short_id_descriptors_.GetDescriptor(id);
-    writer_.WriteBits(short_descriptor, MarkvCodec::kShortDescriptorNumBits);
-
-    if (short_descriptor == 0) {
-      // Forward declared id.
-      return SPV_UNSUPPORTED;
-    }
-
-    mtf = GetMtfShortIdDescriptor(short_descriptor);
-  }
-
-  // Descriptor has been encoded. Now encode the rank of the id in the
-  // associated mtf sequence.
-  return EncodeExistingId(mtf, id);
-}
-
-spv_result_t MarkvEncoder::EncodeExistingId(uint64_t mtf, uint32_t id) {
-  assert(multi_mtf_.GetSize(mtf) > 0);
-  if (multi_mtf_.GetSize(mtf) == 1) {
-    // If the sequence has only one element no need to write rank, the decoder
-    // would make the same decision.
-    return SPV_SUCCESS;
-  }
-
-  uint32_t rank = 0;
-  if (!multi_mtf_.RankFromValue(mtf, id, &rank))
-    return Diag(SPV_ERROR_INTERNAL) << "Id is not in the MTF sequence";
-
-  return EncodeMtfRankHuffman(rank, mtf, kMtfGenericNonZeroRank);
-}
-
-spv_result_t MarkvEncoder::EncodeRefId(uint32_t id) {
-  {
-    // Try to encode using id descriptor mtfs.
-    const spv_result_t result = EncodeIdWithDescriptor(id);
-    if (result != SPV_UNSUPPORTED) return result;
-    // If can't be done continue with other methods.
-  }
-
-  const bool can_forward_declare = spvOperandCanBeForwardDeclaredFunction(
-      SpvOp(inst_.opcode))(operand_index_);
-  uint32_t rank = 0;
-
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kRuleBased) {
-    // Encode using rule-based mtf.
-    uint64_t mtf = GetRuleBasedMtf();
-
-    if (mtf != kMtfNone && !can_forward_declare) {
-      assert(multi_mtf_.HasValue(kMtfAll, id));
-      return EncodeExistingId(mtf, id);
-    }
-
-    if (mtf == kMtfNone) mtf = kMtfAll;
-
-    if (!multi_mtf_.RankFromValue(mtf, id, &rank)) {
-      // This is the first occurrence of a forward declared id.
-      multi_mtf_.Insert(kMtfAll, id);
-      multi_mtf_.Insert(kMtfForwardDeclared, id);
-      if (mtf != kMtfAll) multi_mtf_.Insert(mtf, id);
-      rank = 0;
-    }
-
-    return EncodeMtfRankHuffman(rank, mtf, kMtfAll);
-  } else {
-    assert(can_forward_declare);
-
-    if (!multi_mtf_.RankFromValue(kMtfForwardDeclared, id, &rank)) {
-      // This is the first occurrence of a forward declared id.
-      multi_mtf_.Insert(kMtfForwardDeclared, id);
-      rank = 0;
-    }
-
-    writer_.WriteVariableWidthU32(rank, model_->mtf_rank_chunk_length());
-    return SPV_SUCCESS;
-  }
-}
-
-spv_result_t MarkvEncoder::EncodeTypeId() {
-  if (inst_.opcode == SpvOpFunctionParameter) {
-    assert(!remaining_function_parameter_types_.empty());
-    assert(inst_.type_id == remaining_function_parameter_types_.front());
-    remaining_function_parameter_types_.pop_front();
-    return SPV_SUCCESS;
-  }
-
-  {
-    // Try to encode using id descriptor mtfs.
-    const spv_result_t result = EncodeIdWithDescriptor(inst_.type_id);
-    if (result != SPV_UNSUPPORTED) return result;
-    // If can't be done continue with other methods.
-  }
-
-  assert(model_->id_fallback_strategy() ==
-         MarkvModel::IdFallbackStrategy::kRuleBased);
-
-  uint64_t mtf = GetRuleBasedMtf();
-  assert(!spvOperandCanBeForwardDeclaredFunction(SpvOp(inst_.opcode))(
-      operand_index_));
-
-  if (mtf == kMtfNone) {
-    mtf = kMtfTypeNonFunction;
-    // Function types should have been handled by GetRuleBasedMtf.
-    assert(inst_.opcode != SpvOpFunction);
-  }
-
-  return EncodeExistingId(mtf, inst_.type_id);
-}
-
-spv_result_t MarkvEncoder::EncodeResultId() {
-  uint32_t rank = 0;
-
-  const uint64_t num_still_forward_declared =
-      multi_mtf_.GetSize(kMtfForwardDeclared);
-
-  if (num_still_forward_declared) {
-    // We write the rank only if kMtfForwardDeclared is not empty. If it is
-    // empty the decoder knows that there are no forward declared ids to expect.
-    if (multi_mtf_.RankFromValue(kMtfForwardDeclared, inst_.result_id, &rank)) {
-      // This is a definition of a forward declared id. We can remove the id
-      // from kMtfForwardDeclared.
-      if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id))
-        return Diag(SPV_ERROR_INTERNAL)
-               << "Failed to remove id from kMtfForwardDeclared";
-      writer_.WriteBits(1, 1);
-      writer_.WriteVariableWidthU32(rank, model_->mtf_rank_chunk_length());
-    } else {
-      rank = 0;
-      writer_.WriteBits(0, 1);
-    }
-  }
-
-  if (model_->id_fallback_strategy() ==
-      MarkvModel::IdFallbackStrategy::kRuleBased) {
-    if (!rank) {
-      multi_mtf_.Insert(kMtfAll, inst_.result_id);
-    }
-  }
-
-  return SPV_SUCCESS;
-}
-
-spv_result_t MarkvEncoder::EncodeLiteralNumber(
-    const spv_parsed_operand_t& operand) {
-  if (operand.number_bit_width <= 32) {
-    const uint32_t word = inst_.words[operand.offset];
-    return EncodeNonIdWord(word);
-  } else {
-    assert(operand.number_bit_width <= 64);
-    const uint64_t word = uint64_t(inst_.words[operand.offset]) |
-                          (uint64_t(inst_.words[operand.offset + 1]) << 32);
-    if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) {
-      writer_.WriteVariableWidthU64(word, model_->u64_chunk_length());
-    } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) {
-      int64_t val = 0;
-      std::memcpy(&val, &word, 8);
-      writer_.WriteVariableWidthS64(val, model_->s64_chunk_length(),
-                                    model_->s64_block_exponent());
-    } else if (operand.number_kind == SPV_NUMBER_FLOATING) {
-      writer_.WriteUnencoded(word);
-    } else {
-      return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length";
-    }
-  }
-  return SPV_SUCCESS;
-}
-
-void MarkvEncoder::AddByteBreak(size_t byte_break_if_less_than) {
-  const size_t num_bits_to_next_byte =
-      GetNumBitsToNextByte(writer_.GetNumBits());
-  if (num_bits_to_next_byte == 0 ||
-      num_bits_to_next_byte > byte_break_if_less_than)
-    return;
-
-  if (logger_) {
-    logger_->AppendWhitespaces(kCommentNumWhitespaces);
-    logger_->AppendText("<byte break>");
-  }
-
-  writer_.WriteBits(0, num_bits_to_next_byte);
-}
-
-spv_result_t MarkvEncoder::EncodeInstruction(
-    const spv_parsed_instruction_t& inst) {
-  SpvOp opcode = SpvOp(inst.opcode);
-  inst_ = inst;
-
-  LogDisassemblyInstruction();
-
-  const spv_result_t opcode_encodig_result =
-      EncodeOpcodeAndNumOperands(opcode, inst.num_operands);
-  if (opcode_encodig_result < 0) return opcode_encodig_result;
-
-  if (opcode_encodig_result != SPV_SUCCESS) {
-    // Fallback encoding for opcode and num_operands.
-    writer_.WriteVariableWidthU32(opcode, model_->opcode_chunk_length());
-
-    if (!OpcodeHasFixedNumberOfOperands(opcode)) {
-      // If the opcode has a variable number of operands, encode the number of
-      // operands with the instruction.
-
-      if (logger_) logger_->AppendWhitespaces(kCommentNumWhitespaces);
-
-      writer_.WriteVariableWidthU16(inst.num_operands,
-                                    model_->num_operands_chunk_length());
-    }
-  }
-
-  // Write operands.
-  const uint32_t num_operands = inst_.num_operands;
-  for (operand_index_ = 0; operand_index_ < num_operands; ++operand_index_) {
-    operand_ = inst_.operands[operand_index_];
-
-    if (logger_) {
-      logger_->AppendWhitespaces(kCommentNumWhitespaces);
-      logger_->AppendText("<");
-      logger_->AppendText(spvOperandTypeStr(operand_.type));
-      logger_->AppendText(">");
-    }
-
-    switch (operand_.type) {
-      case SPV_OPERAND_TYPE_RESULT_ID:
-      case SPV_OPERAND_TYPE_TYPE_ID:
-      case SPV_OPERAND_TYPE_ID:
-      case SPV_OPERAND_TYPE_OPTIONAL_ID:
-      case SPV_OPERAND_TYPE_SCOPE_ID:
-      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: {
-        const uint32_t id = inst_.words[operand_.offset];
-        if (operand_.type == SPV_OPERAND_TYPE_TYPE_ID) {
-          const spv_result_t result = EncodeTypeId();
-          if (result != SPV_SUCCESS) return result;
-        } else if (operand_.type == SPV_OPERAND_TYPE_RESULT_ID) {
-          const spv_result_t result = EncodeResultId();
-          if (result != SPV_SUCCESS) return result;
-        } else {
-          const spv_result_t result = EncodeRefId(id);
-          if (result != SPV_SUCCESS) return result;
-        }
-
-        PromoteIfNeeded(id);
-        break;
-      }
-
-      case SPV_OPERAND_TYPE_LITERAL_INTEGER: {
-        const spv_result_t result =
-            EncodeNonIdWord(inst_.words[operand_.offset]);
-        if (result != SPV_SUCCESS) return result;
-        break;
-      }
-
-      case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
-        const spv_result_t result = EncodeLiteralNumber(operand_);
-        if (result != SPV_SUCCESS) return result;
-        break;
-      }
-
-      case SPV_OPERAND_TYPE_LITERAL_STRING: {
-        const char* src =
-            reinterpret_cast<const char*>(&inst_.words[operand_.offset]);
-
-        auto* codec = model_->GetLiteralStringHuffmanCodec(opcode);
-        if (codec) {
-          uint64_t bits = 0;
-          size_t num_bits = 0;
-          const std::string str = src;
-          if (codec->Encode(str, &bits, &num_bits)) {
-            writer_.WriteBits(bits, num_bits);
-            break;
-          } else {
-            bool result =
-                codec->Encode("kMarkvNoneOfTheAbove", &bits, &num_bits);
-            (void)result;
-            assert(result);
-            writer_.WriteBits(bits, num_bits);
-          }
-        }
-
-        const size_t length = spv_strnlen_s(src, operand_.num_words * 4);
-        if (length == operand_.num_words * 4)
-          return Diag(SPV_ERROR_INVALID_BINARY)
-                 << "Failed to find terminal character of literal string";
-        for (size_t i = 0; i < length + 1; ++i) writer_.WriteUnencoded(src[i]);
-        break;
-      }
-
-      default: {
-        for (int i = 0; i < operand_.num_words; ++i) {
-          const uint32_t word = inst_.words[operand_.offset + i];
-          const spv_result_t result = EncodeNonIdWord(word);
-          if (result != SPV_SUCCESS) return result;
-        }
-        break;
-      }
-    }
-  }
-
-  AddByteBreak(MarkvCodec::kByteBreakAfterInstIfLessThanUntilNextByte);
-
-  if (logger_) {
-    logger_->NewLine();
-    logger_->NewLine();
-    if (!logger_->DebugInstruction(inst_)) return SPV_REQUESTED_TERMINATION;
-  }
-
-  ProcessCurInstruction();
-
-  return SPV_SUCCESS;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/markv_encoder.h b/source/comp/markv_encoder.h
deleted file mode 100644
index 2184312..0000000
--- a/source/comp/markv_encoder.h
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/bit_stream.h"
-#include "source/comp/markv.h"
-#include "source/comp/markv_codec.h"
-#include "source/comp/markv_logger.h"
-#include "source/util/make_unique.h"
-
-#ifndef SOURCE_COMP_MARKV_ENCODER_H_
-#define SOURCE_COMP_MARKV_ENCODER_H_
-
-#include <cstring>
-
-namespace spvtools {
-namespace comp {
-
-// SPIR-V to MARK-V encoder. Exposes functions EncodeHeader and
-// EncodeInstruction which can be used as callback by spvBinaryParse.
-// Encoded binary is written to an internally maintained bitstream.
-// After the last instruction is encoded, the resulting MARK-V binary can be
-// acquired by calling GetMarkvBinary().
-//
-// The encoder uses SPIR-V validator to keep internal state, therefore
-// SPIR-V binary needs to be able to pass validator checks.
-// CreateCommentsLogger() can be used to enable the encoder to write comments
-// on how encoding was done, which can later be accessed with GetComments().
-class MarkvEncoder : public MarkvCodec {
- public:
-  // |model| is owned by the caller, must be not null and valid during the
-  // lifetime of MarkvEncoder.
-  MarkvEncoder(spv_const_context context, const MarkvCodecOptions& options,
-               const MarkvModel* model)
-      : MarkvCodec(context, GetValidatorOptions(options), model),
-        options_(options) {}
-  ~MarkvEncoder() override = default;
-
-  // Writes data from SPIR-V header to MARK-V header.
-  spv_result_t EncodeHeader(spv_endianness_t /* endian */, uint32_t /* magic */,
-                            uint32_t version, uint32_t generator,
-                            uint32_t id_bound, uint32_t /* schema */) {
-    SetIdBound(id_bound);
-    header_.spirv_version = version;
-    header_.spirv_generator = generator;
-    return SPV_SUCCESS;
-  }
-
-  // Creates an internal logger which writes comments on the encoding process.
-  void CreateLogger(MarkvLogConsumer log_consumer,
-                    MarkvDebugConsumer debug_consumer) {
-    logger_ = MakeUnique<MarkvLogger>(log_consumer, debug_consumer);
-    writer_.SetCallback(
-        [this](const std::string& str) { logger_->AppendBitSequence(str); });
-  }
-
-  // Encodes SPIR-V instruction to MARK-V and writes to bit stream.
-  // Operation can fail if the instruction fails to pass the validator or if
-  // the encoder stubmles on something unexpected.
-  spv_result_t EncodeInstruction(const spv_parsed_instruction_t& inst);
-
-  // Concatenates MARK-V header and the bit stream with encoded instructions
-  // into a single buffer and returns it as spv_markv_binary. The returned
-  // value is owned by the caller and needs to be destroyed with
-  // spvMarkvBinaryDestroy().
-  std::vector<uint8_t> GetMarkvBinary() {
-    header_.markv_length_in_bits =
-        static_cast<uint32_t>(sizeof(header_) * 8 + writer_.GetNumBits());
-    header_.markv_model =
-        (model_->model_type() << 16) | model_->model_version();
-
-    const size_t num_bytes = sizeof(header_) + writer_.GetDataSizeBytes();
-    std::vector<uint8_t> markv(num_bytes);
-
-    assert(writer_.GetData());
-    std::memcpy(markv.data(), &header_, sizeof(header_));
-    std::memcpy(markv.data() + sizeof(header_), writer_.GetData(),
-                writer_.GetDataSizeBytes());
-    return markv;
-  }
-
-  // Optionally adds disassembly to the comments.
-  // Disassembly should contain all instructions in the module separated by
-  // \n, and no header.
-  void SetDisassembly(std::string&& disassembly) {
-    disassembly_ = MakeUnique<std::stringstream>(std::move(disassembly));
-  }
-
-  // Extracts the next instruction line from the disassembly and logs it.
-  void LogDisassemblyInstruction() {
-    if (logger_ && disassembly_) {
-      std::string line;
-      std::getline(*disassembly_, line, '\n');
-      logger_->AppendTextNewLine(line);
-    }
-  }
-
- private:
-  // Creates and returns validator options. Returned value owned by the caller.
-  static spv_validator_options GetValidatorOptions(
-      const MarkvCodecOptions& options) {
-    return options.validate_spirv_binary ? spvValidatorOptionsCreate()
-                                         : nullptr;
-  }
-
-  // Writes a single word to bit stream. operand_.type determines if the word is
-  // encoded and how.
-  spv_result_t EncodeNonIdWord(uint32_t word);
-
-  // Writes both opcode and num_operands as a single code.
-  // Returns SPV_UNSUPPORTED iff no suitable codec was found.
-  spv_result_t EncodeOpcodeAndNumOperands(uint32_t opcode,
-                                          uint32_t num_operands);
-
-  // Writes mtf rank to bit stream. |mtf| is used to determine the codec
-  // scheme. |fallback_method| is used if no codec defined for |mtf|.
-  spv_result_t EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf,
-                                    uint64_t fallback_method);
-
-  // Writes id using coding based on mtf associated with the id descriptor.
-  // Returns SPV_UNSUPPORTED iff fallback method needs to be used.
-  spv_result_t EncodeIdWithDescriptor(uint32_t id);
-
-  // Writes id using coding based on the given |mtf|, which is expected to
-  // contain the given |id|.
-  spv_result_t EncodeExistingId(uint64_t mtf, uint32_t id);
-
-  // Writes type id of the current instruction if can't be inferred.
-  spv_result_t EncodeTypeId();
-
-  // Writes result id of the current instruction if can't be inferred.
-  spv_result_t EncodeResultId();
-
-  // Writes ids which are neither type nor result ids.
-  spv_result_t EncodeRefId(uint32_t id);
-
-  // Writes bits to the stream until the beginning of the next byte if the
-  // number of bits until the next byte is less than |byte_break_if_less_than|.
-  void AddByteBreak(size_t byte_break_if_less_than);
-
-  // Encodes a literal number operand and writes it to the bit stream.
-  spv_result_t EncodeLiteralNumber(const spv_parsed_operand_t& operand);
-
-  MarkvCodecOptions options_;
-
-  // Bit stream where encoded instructions are written.
-  BitWriterWord64 writer_;
-
-  // If not nullptr, disassembled instruction lines will be written to comments.
-  // Format: \n separated instruction lines, no header.
-  std::unique_ptr<std::stringstream> disassembly_;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_ENCODER_H_
diff --git a/source/comp/markv_logger.h b/source/comp/markv_logger.h
deleted file mode 100644
index c07fe97..0000000
--- a/source/comp/markv_logger.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_COMP_MARKV_LOGGER_H_
-#define SOURCE_COMP_MARKV_LOGGER_H_
-
-#include "source/comp/markv.h"
-
-namespace spvtools {
-namespace comp {
-
-class MarkvLogger {
- public:
-  MarkvLogger(MarkvLogConsumer log_consumer, MarkvDebugConsumer debug_consumer)
-      : log_consumer_(log_consumer), debug_consumer_(debug_consumer) {}
-
-  void AppendText(const std::string& str) {
-    Append(str);
-    use_delimiter_ = false;
-  }
-
-  void AppendTextNewLine(const std::string& str) {
-    Append(str);
-    Append("\n");
-    use_delimiter_ = false;
-  }
-
-  void AppendBitSequence(const std::string& str) {
-    if (debug_consumer_) instruction_bits_ << str;
-    if (use_delimiter_) Append("-");
-    Append(str);
-    use_delimiter_ = true;
-  }
-
-  void AppendWhitespaces(size_t num) {
-    Append(std::string(num, ' '));
-    use_delimiter_ = false;
-  }
-
-  void NewLine() {
-    Append("\n");
-    use_delimiter_ = false;
-  }
-
-  bool DebugInstruction(const spv_parsed_instruction_t& inst) {
-    bool result = true;
-    if (debug_consumer_) {
-      result = debug_consumer_(
-          std::vector<uint32_t>(inst.words, inst.words + inst.num_words),
-          instruction_bits_.str(), instruction_comment_.str());
-      instruction_bits_.str(std::string());
-      instruction_comment_.str(std::string());
-    }
-    return result;
-  }
-
- private:
-  MarkvLogger(const MarkvLogger&) = delete;
-  MarkvLogger(MarkvLogger&&) = delete;
-  MarkvLogger& operator=(const MarkvLogger&) = delete;
-  MarkvLogger& operator=(MarkvLogger&&) = delete;
-
-  void Append(const std::string& str) {
-    if (log_consumer_) log_consumer_(str);
-    if (debug_consumer_) instruction_comment_ << str;
-  }
-
-  MarkvLogConsumer log_consumer_;
-  MarkvDebugConsumer debug_consumer_;
-
-  std::stringstream instruction_bits_;
-  std::stringstream instruction_comment_;
-
-  // If true a delimiter will be appended before the next bit sequence.
-  // Used to generate outputs like: 1100-0 1110-1-1100-1-1111-0 110-0.
-  bool use_delimiter_ = false;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_LOGGER_H_
diff --git a/source/comp/markv_model.h b/source/comp/markv_model.h
deleted file mode 100644
index d03df02..0000000
--- a/source/comp/markv_model.h
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_COMP_MARKV_MODEL_H_
-#define SOURCE_COMP_MARKV_MODEL_H_
-
-#include <unordered_set>
-
-#include "source/comp/huffman_codec.h"
-#include "source/latest_version_spirv_header.h"
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-namespace comp {
-
-// Base class for MARK-V models.
-// The class contains encoding/decoding model with various constants and
-// codecs used by the compression algorithm.
-class MarkvModel {
- public:
-  MarkvModel()
-      : operand_chunk_lengths_(
-            static_cast<size_t>(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES), 0) {
-    // Set default values.
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPE_ID] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_RESULT_ID] = 8;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_ID] = 8;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SCOPE_ID] = 8;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID] = 8;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_LITERAL_INTEGER] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_CAPABILITY] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SOURCE_LANGUAGE] = 3;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODEL] = 3;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_ADDRESSING_MODEL] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_MODEL] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODE] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_STORAGE_CLASS] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_DIMENSIONALITY] = 3;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE] = 3;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_ROUNDING_MODE] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_LINKAGE_TYPE] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_ACCESS_QUALIFIER] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE] = 3;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_DECORATION] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_BUILT_IN] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_GROUP_OPERATION] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO] = 2;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_FAST_MATH_MODE] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_CONTROL] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_LOOP_CONTROL] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_IMAGE] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_IMAGE] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_SELECTION_CONTROL] = 4;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER] = 6;
-    operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER] = 6;
-  }
-
-  uint32_t model_type() const { return model_type_; }
-  uint32_t model_version() const { return model_version_; }
-
-  uint32_t opcode_chunk_length() const { return opcode_chunk_length_; }
-  uint32_t num_operands_chunk_length() const {
-    return num_operands_chunk_length_;
-  }
-  uint32_t mtf_rank_chunk_length() const { return mtf_rank_chunk_length_; }
-
-  uint32_t u64_chunk_length() const { return u64_chunk_length_; }
-  uint32_t s64_chunk_length() const { return s64_chunk_length_; }
-  uint32_t s64_block_exponent() const { return s64_block_exponent_; }
-
-  enum class IdFallbackStrategy {
-    kRuleBased = 0,
-    kShortDescriptor,
-  };
-
-  IdFallbackStrategy id_fallback_strategy() const {
-    return id_fallback_strategy_;
-  }
-
-  // Returns a codec for common opcode_and_num_operands words for the given
-  // previous opcode. May return nullptr if the codec doesn't exist.
-  const HuffmanCodec<uint64_t>* GetOpcodeAndNumOperandsMarkovHuffmanCodec(
-      uint32_t prev_opcode) const {
-    if (prev_opcode == SpvOpNop)
-      return opcode_and_num_operands_huffman_codec_.get();
-
-    const auto it =
-        opcode_and_num_operands_markov_huffman_codecs_.find(prev_opcode);
-    if (it == opcode_and_num_operands_markov_huffman_codecs_.end())
-      return nullptr;
-    return it->second.get();
-  }
-
-  // Returns a codec for common non-id words used for given operand slot.
-  // Operand slot is defined by the opcode and the operand index.
-  // May return nullptr if the codec doesn't exist.
-  const HuffmanCodec<uint64_t>* GetNonIdWordHuffmanCodec(
-      uint32_t opcode, uint32_t operand_index) const {
-    const auto it = non_id_word_huffman_codecs_.find(
-        std::pair<uint32_t, uint32_t>(opcode, operand_index));
-    if (it == non_id_word_huffman_codecs_.end()) return nullptr;
-    return it->second.get();
-  }
-
-  // Returns a codec for common id descriptos used for given operand slot.
-  // Operand slot is defined by the opcode and the operand index.
-  // May return nullptr if the codec doesn't exist.
-  const HuffmanCodec<uint64_t>* GetIdDescriptorHuffmanCodec(
-      uint32_t opcode, uint32_t operand_index) const {
-    const auto it = id_descriptor_huffman_codecs_.find(
-        std::pair<uint32_t, uint32_t>(opcode, operand_index));
-    if (it == id_descriptor_huffman_codecs_.end()) return nullptr;
-    return it->second.get();
-  }
-
-  // Returns a codec for common strings used by the given opcode.
-  // Operand slot is defined by the opcode and the operand index.
-  // May return nullptr if the codec doesn't exist.
-  const HuffmanCodec<std::string>* GetLiteralStringHuffmanCodec(
-      uint32_t opcode) const {
-    const auto it = literal_string_huffman_codecs_.find(opcode);
-    if (it == literal_string_huffman_codecs_.end()) return nullptr;
-    return it->second.get();
-  }
-
-  // Checks if |descriptor| has a coding scheme in any of
-  // id_descriptor_huffman_codecs_.
-  bool DescriptorHasCodingScheme(uint32_t descriptor) const {
-    return descriptors_with_coding_scheme_.count(descriptor);
-  }
-
-  // Checks if any descriptor has a coding scheme.
-  bool AnyDescriptorHasCodingScheme() const {
-    return !descriptors_with_coding_scheme_.empty();
-  }
-
-  // Returns chunk length used for variable length encoding of spirv operand
-  // words.
-  uint32_t GetOperandVariableWidthChunkLength(spv_operand_type_t type) const {
-    return operand_chunk_lengths_.at(static_cast<size_t>(type));
-  }
-
-  // Sets model type.
-  void SetModelType(uint32_t in_model_type) { model_type_ = in_model_type; }
-
-  // Sets model version.
-  void SetModelVersion(uint32_t in_model_version) {
-    model_version_ = in_model_version;
-  }
-
-  // Returns value used by Huffman codecs as a signal that a value is not in the
-  // coding table.
-  static uint64_t GetMarkvNoneOfTheAbove() {
-    // Magic number.
-    return 1111111111111111111;
-  }
-
-  MarkvModel(const MarkvModel&) = delete;
-  const MarkvModel& operator=(const MarkvModel&) = delete;
-
- protected:
-  // Huffman codec for base-rate of opcode_and_num_operands.
-  std::unique_ptr<HuffmanCodec<uint64_t>>
-      opcode_and_num_operands_huffman_codec_;
-
-  // Huffman codecs for opcode_and_num_operands. The map key is previous opcode.
-  std::map<uint32_t, std::unique_ptr<HuffmanCodec<uint64_t>>>
-      opcode_and_num_operands_markov_huffman_codecs_;
-
-  // Huffman codecs for non-id single-word operand values.
-  // The map key is pair <opcode, operand_index>.
-  std::map<std::pair<uint32_t, uint32_t>,
-           std::unique_ptr<HuffmanCodec<uint64_t>>>
-      non_id_word_huffman_codecs_;
-
-  // Huffman codecs for id descriptors. The map key is pair
-  // <opcode, operand_index>.
-  std::map<std::pair<uint32_t, uint32_t>,
-           std::unique_ptr<HuffmanCodec<uint64_t>>>
-      id_descriptor_huffman_codecs_;
-
-  // Set of all descriptors which have a coding scheme in any of
-  // id_descriptor_huffman_codecs_.
-  std::unordered_set<uint32_t> descriptors_with_coding_scheme_;
-
-  // Huffman codecs for literal strings. The map key is the opcode of the
-  // current instruction. This assumes, that there is no more than one literal
-  // string operand per instruction, but would still work even if this is not
-  // the case. Names and debug information strings are not collected.
-  std::map<uint32_t, std::unique_ptr<HuffmanCodec<std::string>>>
-      literal_string_huffman_codecs_;
-
-  // Chunk lengths used for variable width encoding of operands (index is
-  // spv_operand_type of the operand).
-  std::vector<uint32_t> operand_chunk_lengths_;
-
-  uint32_t opcode_chunk_length_ = 7;
-  uint32_t num_operands_chunk_length_ = 3;
-  uint32_t mtf_rank_chunk_length_ = 5;
-
-  uint32_t u64_chunk_length_ = 8;
-  uint32_t s64_chunk_length_ = 8;
-  uint32_t s64_block_exponent_ = 10;
-
-  IdFallbackStrategy id_fallback_strategy_ =
-      IdFallbackStrategy::kShortDescriptor;
-
-  uint32_t model_type_ = 0;
-  uint32_t model_version_ = 0;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MARKV_MODEL_H_
diff --git a/source/comp/move_to_front.cpp b/source/comp/move_to_front.cpp
deleted file mode 100644
index 9d35a3f..0000000
--- a/source/comp/move_to_front.cpp
+++ /dev/null
@@ -1,456 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/comp/move_to_front.h"
-
-#include <algorithm>
-#include <iomanip>
-#include <iostream>
-#include <ostream>
-#include <sstream>
-#include <unordered_set>
-#include <utility>
-
-namespace spvtools {
-namespace comp {
-
-bool MoveToFront::Insert(uint32_t value) {
-  auto it = value_to_node_.find(value);
-  if (it != value_to_node_.end() && IsInTree(it->second)) return false;
-
-  const uint32_t old_size = GetSize();
-  (void)old_size;
-
-  InsertNode(CreateNode(next_timestamp_++, value));
-
-  last_accessed_value_ = value;
-  last_accessed_value_valid_ = true;
-
-  assert(value_to_node_.count(value));
-  assert(old_size + 1 == GetSize());
-  return true;
-}
-
-bool MoveToFront::Remove(uint32_t value) {
-  auto it = value_to_node_.find(value);
-  if (it == value_to_node_.end()) return false;
-
-  if (!IsInTree(it->second)) return false;
-
-  if (last_accessed_value_ == value) last_accessed_value_valid_ = false;
-
-  const uint32_t orphan = RemoveNode(it->second);
-  (void)orphan;
-  // The node of |value| is still alive but it's orphaned now. Can still be
-  // reused later.
-  assert(!IsInTree(orphan));
-  assert(ValueOf(orphan) == value);
-  return true;
-}
-
-bool MoveToFront::RankFromValue(uint32_t value, uint32_t* rank) {
-  if (last_accessed_value_valid_ && last_accessed_value_ == value) {
-    *rank = 1;
-    return true;
-  }
-
-  const uint32_t old_size = GetSize();
-  if (old_size == 1) {
-    if (ValueOf(root_) == value) {
-      *rank = 1;
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  const auto it = value_to_node_.find(value);
-  if (it == value_to_node_.end()) {
-    return false;
-  }
-
-  uint32_t target = it->second;
-
-  if (!IsInTree(target)) {
-    return false;
-  }
-
-  uint32_t node = target;
-  *rank = 1 + SizeOf(LeftOf(node));
-  while (node) {
-    if (IsRightChild(node)) *rank += 1 + SizeOf(LeftOf(ParentOf(node)));
-    node = ParentOf(node);
-  }
-
-  // Don't update timestamp if the node has rank 1.
-  if (*rank != 1) {
-    // Update timestamp and reposition the node.
-    target = RemoveNode(target);
-    assert(ValueOf(target) == value);
-    assert(old_size == GetSize() + 1);
-    MutableTimestampOf(target) = next_timestamp_++;
-    InsertNode(target);
-    assert(old_size == GetSize());
-  }
-
-  last_accessed_value_ = value;
-  last_accessed_value_valid_ = true;
-  return true;
-}
-
-bool MoveToFront::HasValue(uint32_t value) const {
-  const auto it = value_to_node_.find(value);
-  if (it == value_to_node_.end()) {
-    return false;
-  }
-
-  return IsInTree(it->second);
-}
-
-bool MoveToFront::Promote(uint32_t value) {
-  if (last_accessed_value_valid_ && last_accessed_value_ == value) {
-    return true;
-  }
-
-  const uint32_t old_size = GetSize();
-  if (old_size == 1) return ValueOf(root_) == value;
-
-  const auto it = value_to_node_.find(value);
-  if (it == value_to_node_.end()) {
-    return false;
-  }
-
-  uint32_t target = it->second;
-
-  if (!IsInTree(target)) {
-    return false;
-  }
-
-  // Update timestamp and reposition the node.
-  target = RemoveNode(target);
-  assert(ValueOf(target) == value);
-  assert(old_size == GetSize() + 1);
-
-  MutableTimestampOf(target) = next_timestamp_++;
-  InsertNode(target);
-  assert(old_size == GetSize());
-
-  last_accessed_value_ = value;
-  last_accessed_value_valid_ = true;
-  return true;
-}
-
-bool MoveToFront::ValueFromRank(uint32_t rank, uint32_t* value) {
-  if (last_accessed_value_valid_ && rank == 1) {
-    *value = last_accessed_value_;
-    return true;
-  }
-
-  const uint32_t old_size = GetSize();
-  if (rank <= 0 || rank > old_size) {
-    return false;
-  }
-
-  if (old_size == 1) {
-    *value = ValueOf(root_);
-    return true;
-  }
-
-  const bool update_timestamp = (rank != 1);
-
-  uint32_t node = root_;
-  while (node) {
-    const uint32_t left_subtree_num_nodes = SizeOf(LeftOf(node));
-    if (rank == left_subtree_num_nodes + 1) {
-      // This is the node we are looking for.
-      // Don't update timestamp if the node has rank 1.
-      if (update_timestamp) {
-        node = RemoveNode(node);
-        assert(old_size == GetSize() + 1);
-        MutableTimestampOf(node) = next_timestamp_++;
-        InsertNode(node);
-        assert(old_size == GetSize());
-      }
-      *value = ValueOf(node);
-      last_accessed_value_ = *value;
-      last_accessed_value_valid_ = true;
-      return true;
-    }
-
-    if (rank < left_subtree_num_nodes + 1) {
-      // Descend into the left subtree. The rank is still valid.
-      node = LeftOf(node);
-    } else {
-      // Descend into the right subtree. We leave behind the left subtree and
-      // the current node, adjust the |rank| accordingly.
-      rank -= left_subtree_num_nodes + 1;
-      node = RightOf(node);
-    }
-  }
-
-  assert(0);
-  return false;
-}
-
-uint32_t MoveToFront::CreateNode(uint32_t timestamp, uint32_t value) {
-  uint32_t handle = static_cast<uint32_t>(nodes_.size());
-  const auto result = value_to_node_.emplace(value, handle);
-  if (result.second) {
-    // Create new node.
-    nodes_.emplace_back(Node());
-    Node& node = nodes_.back();
-    node.timestamp = timestamp;
-    node.value = value;
-    node.size = 1;
-    // Non-NIL nodes start with height 1 because their NIL children are
-    // leaves.
-    node.height = 1;
-  } else {
-    // Reuse old node.
-    handle = result.first->second;
-    assert(!IsInTree(handle));
-    assert(ValueOf(handle) == value);
-    assert(SizeOf(handle) == 1);
-    assert(HeightOf(handle) == 1);
-    MutableTimestampOf(handle) = timestamp;
-  }
-
-  return handle;
-}
-
-void MoveToFront::InsertNode(uint32_t node) {
-  assert(!IsInTree(node));
-  assert(SizeOf(node) == 1);
-  assert(HeightOf(node) == 1);
-  assert(TimestampOf(node));
-
-  if (!root_) {
-    root_ = node;
-    return;
-  }
-
-  uint32_t iter = root_;
-  uint32_t parent = 0;
-
-  // Will determine if |node| will become the right or left child after
-  // insertion (but before balancing).
-  bool right_child = true;
-
-  // Find the node which will become |node|'s parent after insertion
-  // (but before balancing).
-  while (iter) {
-    parent = iter;
-    assert(TimestampOf(iter) != TimestampOf(node));
-    right_child = TimestampOf(iter) > TimestampOf(node);
-    iter = right_child ? RightOf(iter) : LeftOf(iter);
-  }
-
-  assert(parent);
-
-  // Connect node and parent.
-  MutableParentOf(node) = parent;
-  if (right_child)
-    MutableRightOf(parent) = node;
-  else
-    MutableLeftOf(parent) = node;
-
-  // Insertion is finished. Start the balancing process.
-  bool needs_rebalancing = true;
-  parent = ParentOf(node);
-
-  while (parent) {
-    UpdateNode(parent);
-
-    if (needs_rebalancing) {
-      const int parent_balance = BalanceOf(parent);
-
-      if (RightOf(parent) == node) {
-        // Added node to the right subtree.
-        if (parent_balance > 1) {
-          // Parent is right heavy, rotate left.
-          if (BalanceOf(node) < 0) RotateRight(node);
-          parent = RotateLeft(parent);
-        } else if (parent_balance == 0 || parent_balance == -1) {
-          // Parent is balanced or left heavy, no need to balance further.
-          needs_rebalancing = false;
-        }
-      } else {
-        // Added node to the left subtree.
-        if (parent_balance < -1) {
-          // Parent is left heavy, rotate right.
-          if (BalanceOf(node) > 0) RotateLeft(node);
-          parent = RotateRight(parent);
-        } else if (parent_balance == 0 || parent_balance == 1) {
-          // Parent is balanced or right heavy, no need to balance further.
-          needs_rebalancing = false;
-        }
-      }
-    }
-
-    assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1));
-
-    node = parent;
-    parent = ParentOf(parent);
-  }
-}
-
-uint32_t MoveToFront::RemoveNode(uint32_t node) {
-  if (LeftOf(node) && RightOf(node)) {
-    // If |node| has two children, then use another node as scapegoat and swap
-    // their contents. We pick the scapegoat on the side of the tree which has
-    // more nodes.
-    const uint32_t scapegoat = SizeOf(LeftOf(node)) >= SizeOf(RightOf(node))
-                                   ? RightestDescendantOf(LeftOf(node))
-                                   : LeftestDescendantOf(RightOf(node));
-    assert(scapegoat);
-    std::swap(MutableValueOf(node), MutableValueOf(scapegoat));
-    std::swap(MutableTimestampOf(node), MutableTimestampOf(scapegoat));
-    value_to_node_[ValueOf(node)] = node;
-    value_to_node_[ValueOf(scapegoat)] = scapegoat;
-    node = scapegoat;
-  }
-
-  // |node| may have only one child at this point.
-  assert(!RightOf(node) || !LeftOf(node));
-
-  uint32_t parent = ParentOf(node);
-  uint32_t child = RightOf(node) ? RightOf(node) : LeftOf(node);
-
-  // Orphan |node| and reconnect parent and child.
-  if (child) MutableParentOf(child) = parent;
-
-  if (parent) {
-    if (LeftOf(parent) == node)
-      MutableLeftOf(parent) = child;
-    else
-      MutableRightOf(parent) = child;
-  }
-
-  MutableParentOf(node) = 0;
-  MutableLeftOf(node) = 0;
-  MutableRightOf(node) = 0;
-  UpdateNode(node);
-  const uint32_t orphan = node;
-
-  if (root_ == node) root_ = child;
-
-  // Removal is finished. Start the balancing process.
-  bool needs_rebalancing = true;
-  node = child;
-
-  while (parent) {
-    UpdateNode(parent);
-
-    if (needs_rebalancing) {
-      const int parent_balance = BalanceOf(parent);
-
-      if (parent_balance == 1 || parent_balance == -1) {
-        // The height of the subtree was not changed.
-        needs_rebalancing = false;
-      } else {
-        if (RightOf(parent) == node) {
-          // Removed node from the right subtree.
-          if (parent_balance < -1) {
-            // Parent is left heavy, rotate right.
-            const uint32_t sibling = LeftOf(parent);
-            if (BalanceOf(sibling) > 0) RotateLeft(sibling);
-            parent = RotateRight(parent);
-          }
-        } else {
-          // Removed node from the left subtree.
-          if (parent_balance > 1) {
-            // Parent is right heavy, rotate left.
-            const uint32_t sibling = RightOf(parent);
-            if (BalanceOf(sibling) < 0) RotateRight(sibling);
-            parent = RotateLeft(parent);
-          }
-        }
-      }
-    }
-
-    assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1));
-
-    node = parent;
-    parent = ParentOf(parent);
-  }
-
-  return orphan;
-}
-
-uint32_t MoveToFront::RotateLeft(const uint32_t node) {
-  const uint32_t pivot = RightOf(node);
-  assert(pivot);
-
-  // LeftOf(pivot) gets attached to node in place of pivot.
-  MutableRightOf(node) = LeftOf(pivot);
-  if (RightOf(node)) MutableParentOf(RightOf(node)) = node;
-
-  // Pivot gets attached to ParentOf(node) in place of node.
-  MutableParentOf(pivot) = ParentOf(node);
-  if (!ParentOf(node))
-    root_ = pivot;
-  else if (IsLeftChild(node))
-    MutableLeftOf(ParentOf(node)) = pivot;
-  else
-    MutableRightOf(ParentOf(node)) = pivot;
-
-  // Node is child of pivot.
-  MutableLeftOf(pivot) = node;
-  MutableParentOf(node) = pivot;
-
-  // Update both node and pivot. Pivot is the new parent of node, so node should
-  // be updated first.
-  UpdateNode(node);
-  UpdateNode(pivot);
-
-  return pivot;
-}
-
-uint32_t MoveToFront::RotateRight(const uint32_t node) {
-  const uint32_t pivot = LeftOf(node);
-  assert(pivot);
-
-  // RightOf(pivot) gets attached to node in place of pivot.
-  MutableLeftOf(node) = RightOf(pivot);
-  if (LeftOf(node)) MutableParentOf(LeftOf(node)) = node;
-
-  // Pivot gets attached to ParentOf(node) in place of node.
-  MutableParentOf(pivot) = ParentOf(node);
-  if (!ParentOf(node))
-    root_ = pivot;
-  else if (IsLeftChild(node))
-    MutableLeftOf(ParentOf(node)) = pivot;
-  else
-    MutableRightOf(ParentOf(node)) = pivot;
-
-  // Node is child of pivot.
-  MutableRightOf(pivot) = node;
-  MutableParentOf(node) = pivot;
-
-  // Update both node and pivot. Pivot is the new parent of node, so node should
-  // be updated first.
-  UpdateNode(node);
-  UpdateNode(pivot);
-
-  return pivot;
-}
-
-void MoveToFront::UpdateNode(uint32_t node) {
-  MutableSizeOf(node) = 1 + SizeOf(LeftOf(node)) + SizeOf(RightOf(node));
-  MutableHeightOf(node) =
-      1 + std::max(HeightOf(LeftOf(node)), HeightOf(RightOf(node)));
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/source/comp/move_to_front.h b/source/comp/move_to_front.h
deleted file mode 100644
index 8752194..0000000
--- a/source/comp/move_to_front.h
+++ /dev/null
@@ -1,384 +0,0 @@
-// 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.
-
-#ifndef SOURCE_COMP_MOVE_TO_FRONT_H_
-#define SOURCE_COMP_MOVE_TO_FRONT_H_
-
-#include <cassert>
-#include <cstdint>
-#include <map>
-#include <set>
-#include <unordered_map>
-#include <vector>
-
-namespace spvtools {
-namespace comp {
-
-// Log(n) move-to-front implementation. Implements the following functions:
-// Insert - pushes value to the front of the mtf sequence
-//          (only unique values allowed).
-// Remove - remove value from the sequence.
-// ValueFromRank - access value by its 1-indexed rank in the sequence.
-// RankFromValue - get the rank of the given value in the sequence.
-// Accessing a value with ValueFromRank or RankFromValue moves the value to the
-// front of the sequence (rank of 1).
-//
-// The implementation is based on an AVL-based order statistic tree. The tree
-// is ordered by timestamps issued when values are inserted or accessed (recent
-// values go to the left side of the tree, old values are gradually rotated to
-// the right side).
-//
-// Terminology
-// rank: 1-indexed rank showing how recently the value was inserted or accessed.
-// node: handle used internally to access node data.
-// size: size of the subtree of a node (including the node).
-// height: distance from a node to the farthest leaf.
-class MoveToFront {
- public:
-  explicit MoveToFront(size_t reserve_capacity = 4) {
-    nodes_.reserve(reserve_capacity);
-
-    // Create NIL node.
-    nodes_.emplace_back(Node());
-  }
-
-  virtual ~MoveToFront() = default;
-
-  // Inserts value in the move-to-front sequence. Does nothing if the value is
-  // already in the sequence. Returns true if insertion was successful.
-  // The inserted value is placed at the front of the sequence (rank 1).
-  bool Insert(uint32_t value);
-
-  // Removes value from move-to-front sequence. Returns false iff the value
-  // was not found.
-  bool Remove(uint32_t value);
-
-  // Computes 1-indexed rank of value in the move-to-front sequence and moves
-  // the value to the front. Example:
-  // Before the call: 4 8 2 1 7
-  // RankFromValue(8) returns 2
-  // After the call: 8 4 2 1 7
-  // Returns true iff the value was found in the sequence.
-  bool RankFromValue(uint32_t value, uint32_t* rank);
-
-  // Returns value corresponding to a 1-indexed rank in the move-to-front
-  // sequence and moves the value to the front. Example:
-  // Before the call: 4 8 2 1 7
-  // ValueFromRank(2) returns 8
-  // After the call: 8 4 2 1 7
-  // Returns true iff the rank is within bounds [1, GetSize()].
-  bool ValueFromRank(uint32_t rank, uint32_t* value);
-
-  // Moves the value to the front of the sequence.
-  // Returns false iff value is not in the sequence.
-  bool Promote(uint32_t value);
-
-  // Returns true iff the move-to-front sequence contains the value.
-  bool HasValue(uint32_t value) const;
-
-  // Returns the number of elements in the move-to-front sequence.
-  uint32_t GetSize() const { return SizeOf(root_); }
-
- protected:
-  // Internal tree data structure uses handles instead of pointers. Leaves and
-  // root parent reference a singleton under handle 0. Although dereferencing
-  // a null pointer is not possible, inappropriate access to handle 0 would
-  // cause an assertion. Handles are not garbage collected if value was
-  // deprecated
-  // with DeprecateValue(). But handles are recycled when a node is
-  // repositioned.
-
-  // Internal tree data structure node.
-  struct Node {
-    // Timestamp from a logical clock which updates every time the element is
-    // accessed through ValueFromRank or RankFromValue.
-    uint32_t timestamp = 0;
-    // The size of the node's subtree, including the node.
-    // SizeOf(LeftOf(node)) + SizeOf(RightOf(node)) + 1.
-    uint32_t size = 0;
-    // Handles to connected nodes.
-    uint32_t left = 0;
-    uint32_t right = 0;
-    uint32_t parent = 0;
-    // Distance to the farthest leaf.
-    // Leaves have height 0, real nodes at least 1.
-    uint32_t height = 0;
-    // Stored value.
-    uint32_t value = 0;
-  };
-
-  // Creates node and sets correct values. Non-NIL nodes should be created only
-  // through this function. If the node with this value has been created
-  // previously
-  // and since orphaned, reuses the old node instead of creating a new one.
-  uint32_t CreateNode(uint32_t timestamp, uint32_t value);
-
-  // Node accessor methods. Naming is designed to be similar to natural
-  // language as these functions tend to be used in sequences, for example:
-  // ParentOf(LeftestDescendentOf(RightOf(node)))
-
-  // Returns value of the node referenced by |handle|.
-  uint32_t ValueOf(uint32_t node) const { return nodes_.at(node).value; }
-
-  // Returns left child of |node|.
-  uint32_t LeftOf(uint32_t node) const { return nodes_.at(node).left; }
-
-  // Returns right child of |node|.
-  uint32_t RightOf(uint32_t node) const { return nodes_.at(node).right; }
-
-  // Returns parent of |node|.
-  uint32_t ParentOf(uint32_t node) const { return nodes_.at(node).parent; }
-
-  // Returns timestamp of |node|.
-  uint32_t TimestampOf(uint32_t node) const {
-    assert(node);
-    return nodes_.at(node).timestamp;
-  }
-
-  // Returns size of |node|.
-  uint32_t SizeOf(uint32_t node) const { return nodes_.at(node).size; }
-
-  // Returns height of |node|.
-  uint32_t HeightOf(uint32_t node) const { return nodes_.at(node).height; }
-
-  // Returns mutable reference to value of |node|.
-  uint32_t& MutableValueOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).value;
-  }
-
-  // Returns mutable reference to handle of left child of |node|.
-  uint32_t& MutableLeftOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).left;
-  }
-
-  // Returns mutable reference to handle of right child of |node|.
-  uint32_t& MutableRightOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).right;
-  }
-
-  // Returns mutable reference to handle of parent of |node|.
-  uint32_t& MutableParentOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).parent;
-  }
-
-  // Returns mutable reference to timestamp of |node|.
-  uint32_t& MutableTimestampOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).timestamp;
-  }
-
-  // Returns mutable reference to size of |node|.
-  uint32_t& MutableSizeOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).size;
-  }
-
-  // Returns mutable reference to height of |node|.
-  uint32_t& MutableHeightOf(uint32_t node) {
-    assert(node);
-    return nodes_.at(node).height;
-  }
-
-  // Returns true iff |node| is left child of its parent.
-  bool IsLeftChild(uint32_t node) const {
-    assert(node);
-    return LeftOf(ParentOf(node)) == node;
-  }
-
-  // Returns true iff |node| is right child of its parent.
-  bool IsRightChild(uint32_t node) const {
-    assert(node);
-    return RightOf(ParentOf(node)) == node;
-  }
-
-  // Returns true iff |node| has no relatives.
-  bool IsOrphan(uint32_t node) const {
-    assert(node);
-    return !ParentOf(node) && !LeftOf(node) && !RightOf(node);
-  }
-
-  // Returns true iff |node| is in the tree.
-  bool IsInTree(uint32_t node) const {
-    assert(node);
-    return node == root_ || !IsOrphan(node);
-  }
-
-  // Returns the height difference between right and left subtrees.
-  int BalanceOf(uint32_t node) const {
-    return int(HeightOf(RightOf(node))) - int(HeightOf(LeftOf(node)));
-  }
-
-  // Updates size and height of the node, assuming that the children have
-  // correct values.
-  void UpdateNode(uint32_t node);
-
-  // Returns the most LeftOf(LeftOf(... descendent which is not leaf.
-  uint32_t LeftestDescendantOf(uint32_t node) const {
-    uint32_t parent = 0;
-    while (node) {
-      parent = node;
-      node = LeftOf(node);
-    }
-    return parent;
-  }
-
-  // Returns the most RightOf(RightOf(... descendent which is not leaf.
-  uint32_t RightestDescendantOf(uint32_t node) const {
-    uint32_t parent = 0;
-    while (node) {
-      parent = node;
-      node = RightOf(node);
-    }
-    return parent;
-  }
-
-  // Inserts node in the tree. The node must be an orphan.
-  void InsertNode(uint32_t node);
-
-  // Removes node from the tree. May change value_to_node_ if removal uses a
-  // scapegoat. Returns the removed (orphaned) handle for recycling. The
-  // returned handle may not be equal to |node| if scapegoat was used.
-  uint32_t RemoveNode(uint32_t node);
-
-  // Rotates |node| left, reassigns all connections and returns the node
-  // which takes place of the |node|.
-  uint32_t RotateLeft(const uint32_t node);
-
-  // Rotates |node| right, reassigns all connections and returns the node
-  // which takes place of the |node|.
-  uint32_t RotateRight(const uint32_t node);
-
-  // Root node handle. The tree is empty if root_ is 0.
-  uint32_t root_ = 0;
-
-  // Incremented counters for next timestamp and value.
-  uint32_t next_timestamp_ = 1;
-
-  // Holds all tree nodes. Indices of this vector are node handles.
-  std::vector<Node> nodes_;
-
-  // Maps ids to node handles.
-  std::unordered_map<uint32_t, uint32_t> value_to_node_;
-
-  // Cache for the last accessed value in the sequence.
-  uint32_t last_accessed_value_ = 0;
-  bool last_accessed_value_valid_ = false;
-};
-
-class MultiMoveToFront {
- public:
-  // Inserts |value| to sequence with handle |mtf|.
-  // Returns false if |mtf| already has |value|.
-  bool Insert(uint64_t mtf, uint32_t value) {
-    if (GetMtf(mtf).Insert(value)) {
-      val_to_mtfs_[value].insert(mtf);
-      return true;
-    }
-    return false;
-  }
-
-  // Removes |value| from sequence with handle |mtf|.
-  // Returns false if |mtf| doesn't have |value|.
-  bool Remove(uint64_t mtf, uint32_t value) {
-    if (GetMtf(mtf).Remove(value)) {
-      val_to_mtfs_[value].erase(mtf);
-      return true;
-    }
-    assert(val_to_mtfs_[value].count(mtf) == 0);
-    return false;
-  }
-
-  // Removes |value| from all sequences which have it.
-  void RemoveFromAll(uint32_t value) {
-    auto it = val_to_mtfs_.find(value);
-    if (it == val_to_mtfs_.end()) return;
-
-    auto& mtfs_containing_value = it->second;
-    for (uint64_t mtf : mtfs_containing_value) {
-      GetMtf(mtf).Remove(value);
-    }
-
-    val_to_mtfs_.erase(value);
-  }
-
-  // Computes rank of |value| in sequence |mtf|.
-  // Returns false if |mtf| doesn't have |value|.
-  bool RankFromValue(uint64_t mtf, uint32_t value, uint32_t* rank) {
-    return GetMtf(mtf).RankFromValue(value, rank);
-  }
-
-  // Finds |value| with |rank| in sequence |mtf|.
-  // Returns false if |rank| is out of bounds.
-  bool ValueFromRank(uint64_t mtf, uint32_t rank, uint32_t* value) {
-    return GetMtf(mtf).ValueFromRank(rank, value);
-  }
-
-  // Returns size of |mtf| sequence.
-  uint32_t GetSize(uint64_t mtf) { return GetMtf(mtf).GetSize(); }
-
-  // Promotes |value| in all sequences which have it.
-  void Promote(uint32_t value) {
-    const auto it = val_to_mtfs_.find(value);
-    if (it == val_to_mtfs_.end()) return;
-
-    const auto& mtfs_containing_value = it->second;
-    for (uint64_t mtf : mtfs_containing_value) {
-      GetMtf(mtf).Promote(value);
-    }
-  }
-
-  // Inserts |value| in sequence |mtf| or promotes if it's already there.
-  void InsertOrPromote(uint64_t mtf, uint32_t value) {
-    if (!Insert(mtf, value)) {
-      GetMtf(mtf).Promote(value);
-    }
-  }
-
-  // Returns if |mtf| sequence has |value|.
-  bool HasValue(uint64_t mtf, uint32_t value) {
-    return GetMtf(mtf).HasValue(value);
-  }
-
- private:
-  // Returns actual MoveToFront object corresponding to |handle|.
-  // As multiple operations are often performed consecutively for the same
-  // sequence, the last returned value is cached.
-  MoveToFront& GetMtf(uint64_t handle) {
-    if (!cached_mtf_ || cached_handle_ != handle) {
-      cached_handle_ = handle;
-      cached_mtf_ = &mtfs_[handle];
-    }
-
-    return *cached_mtf_;
-  }
-
-  // Container holding MoveToFront objects. Map key is sequence handle.
-  std::map<uint64_t, MoveToFront> mtfs_;
-
-  // Container mapping value to sequences which contain that value.
-  std::unordered_map<uint32_t, std::set<uint64_t>> val_to_mtfs_;
-
-  // Cache for the last accessed sequence.
-  uint64_t cached_handle_ = 0;
-  MoveToFront* cached_mtf_ = nullptr;
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // SOURCE_COMP_MOVE_TO_FRONT_H_
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 08c775e..1198f76 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -30,6 +30,7 @@
 #include "glsl.std.450.insts.inc"
 #include "opencl.std.insts.inc"
 
+#include "spirv-tools/libspirv.h"
 #include "spv-amd-gcn-shader.insts.inc"
 #include "spv-amd-shader-ballot.insts.inc"
 #include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
@@ -80,7 +81,9 @@
     case SPV_ENV_OPENGL_4_5:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       *pExtInstTable = &kTable_1_0;
       return SPV_SUCCESS;
     default:
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..fbabba1
--- /dev/null
+++ b/source/fuzz/CMakeLists.txt
@@ -0,0 +1,138 @@
+# Copyright (c) 2019 Google LLC
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if(SPIRV_BUILD_FUZZER)
+  set(PROTOBUF_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/spvtoolsfuzz.proto)
+
+  add_custom_command(
+        OUTPUT protobufs/spvtoolsfuzz.pb.cc protobufs/spvtoolsfuzz.pb.h
+        COMMAND protobuf::protoc
+        -I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs
+        --cpp_out=protobufs
+        ${PROTOBUF_SOURCE}
+        DEPENDS ${PROTOBUF_SOURCE}
+        COMMENT "Generate protobuf sources from proto definition file."
+  )
+
+  set(SPIRV_TOOLS_FUZZ_SOURCES
+        fact_manager.h
+        fuzzer.h
+        fuzzer_context.h
+        fuzzer_pass.h
+        fuzzer_pass_add_dead_breaks.h
+        fuzzer_pass_add_dead_continues.h
+        fuzzer_pass_add_useful_constructs.h
+        fuzzer_pass_obfuscate_constants.h
+        fuzzer_pass_permute_blocks.h
+        fuzzer_pass_split_blocks.h
+        fuzzer_util.h
+        id_use_descriptor.h
+        protobufs/spirvfuzz_protobufs.h
+        pseudo_random_generator.h
+        random_generator.h
+        replayer.h
+        shrinker.h
+        transformation.h
+        transformation_add_constant_boolean.h
+        transformation_add_constant_scalar.h
+        transformation_add_dead_break.h
+        transformation_add_dead_continue.h
+        transformation_add_type_boolean.h
+        transformation_add_type_float.h
+        transformation_add_type_int.h
+        transformation_add_type_pointer.h
+        transformation_move_block_down.h
+        transformation_replace_boolean_constant_with_constant_binary.h
+        transformation_replace_constant_with_uniform.h
+        transformation_split_block.h
+        uniform_buffer_element_descriptor.h
+        ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
+
+        fact_manager.cpp
+        fuzzer.cpp
+        fuzzer_context.cpp
+        fuzzer_pass.cpp
+        fuzzer_pass_add_dead_breaks.cpp
+        fuzzer_pass_add_dead_continues.cpp
+        fuzzer_pass_add_useful_constructs.cpp
+        fuzzer_pass_obfuscate_constants.cpp
+        fuzzer_pass_permute_blocks.cpp
+        fuzzer_pass_split_blocks.cpp
+        fuzzer_util.cpp
+        id_use_descriptor.cpp
+        pseudo_random_generator.cpp
+        random_generator.cpp
+        replayer.cpp
+        shrinker.cpp
+        transformation.cpp
+        transformation_add_constant_boolean.cpp
+        transformation_add_constant_scalar.cpp
+        transformation_add_dead_break.cpp
+        transformation_add_dead_continue.cpp
+        transformation_add_type_boolean.cpp
+        transformation_add_type_float.cpp
+        transformation_add_type_int.cpp
+        transformation_add_type_pointer.cpp
+        transformation_move_block_down.cpp
+        transformation_replace_boolean_constant_with_constant_binary.cpp
+        transformation_replace_constant_with_uniform.cpp
+        transformation_split_block.cpp
+        uniform_buffer_element_descriptor.cpp
+        ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
+        )
+
+  if(MSVC)
+    # Enable parallel builds across four cores for this lib
+    add_definitions(/MP4)
+  endif()
+
+  spvtools_pch(SPIRV_TOOLS_FUZZ_SOURCES pch_source_fuzz)
+
+  add_library(SPIRV-Tools-fuzz ${SPIRV_TOOLS_FUZZ_SOURCES})
+
+  spvtools_default_compile_options(SPIRV-Tools-fuzz)
+  target_compile_definitions(SPIRV-Tools-fuzz PUBLIC -DGOOGLE_PROTOBUF_NO_RTTI -DGOOGLE_PROTOBUF_USE_UNALIGNED=0)
+
+  # Compilation of the auto-generated protobuf source file will yield warnings,
+  # which we have no control over and thus wish to ignore.
+  if(${COMPILER_IS_LIKE_GNU})
+    set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc PROPERTIES COMPILE_FLAGS -w)
+  endif()
+  if(MSVC)
+    set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc PROPERTIES COMPILE_FLAGS /w)
+  endif()
+
+  target_include_directories(SPIRV-Tools-fuzz
+        PUBLIC ${spirv-tools_SOURCE_DIR}/include
+        PUBLIC ${SPIRV_HEADER_INCLUDE_DIR}
+        PRIVATE ${spirv-tools_BINARY_DIR}
+        PRIVATE ${CMAKE_BINARY_DIR})
+
+  # The fuzzer reuses a lot of functionality from the SPIRV-Tools library.
+  target_link_libraries(SPIRV-Tools-fuzz
+        PUBLIC ${SPIRV_TOOLS}
+        PUBLIC SPIRV-Tools-opt
+        PUBLIC protobuf::libprotobuf)
+
+  set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
+  spvtools_check_symbol_exports(SPIRV-Tools-fuzz)
+
+  if(ENABLE_SPIRV_TOOLS_INSTALL)
+      install(TARGETS SPIRV-Tools-fuzz
+            RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+            LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+            ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  endif(ENABLE_SPIRV_TOOLS_INSTALL)
+
+endif(SPIRV_BUILD_FUZZER)
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
new file mode 100644
index 0000000..442ff16
--- /dev/null
+++ b/source/fuzz/fact_manager.cpp
@@ -0,0 +1,393 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager.h"
+
+#include <sstream>
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+
+std::string ToString(const protobufs::Fact& fact) {
+  assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
+         "Right now this is the only fact.");
+  std::stringstream stream;
+  stream << "("
+         << fact.constant_uniform_fact()
+                .uniform_buffer_element_descriptor()
+                .descriptor_set()
+         << ", "
+         << fact.constant_uniform_fact()
+                .uniform_buffer_element_descriptor()
+                .binding()
+         << ")[";
+
+  bool first = true;
+  for (auto index : fact.constant_uniform_fact()
+                        .uniform_buffer_element_descriptor()
+                        .index()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << index;
+  }
+
+  stream << "] == [";
+
+  first = true;
+  for (auto constant_word : fact.constant_uniform_fact().constant_word()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << constant_word;
+  }
+
+  stream << "]";
+  return stream.str();
+}
+
+}  // namespace
+
+// The purpose of this struct is to group the fields and data used to represent
+// facts about uniform constants.
+struct FactManager::ConstantUniformFacts {
+  // See method in FactManager which delegates to this method.
+  bool AddFact(const protobufs::FactConstantUniform& fact,
+               opt::IRContext* context);
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+      opt::IRContext* ir_context, uint32_t type_id) const;
+
+  // See method in FactManager which delegates to this method.
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+                                   uint32_t constant_id) const;
+
+  // See method in FactManager which delegates to this method.
+  uint32_t GetConstantFromUniformDescriptor(
+      opt::IRContext* context,
+      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+      const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
+
+  // Returns true if and only if the words associated with
+  // |constant_instruction| exactly match the words for the constant associated
+  // with |constant_uniform_fact|.
+  bool DataMatches(
+      const opt::Instruction& constant_instruction,
+      const protobufs::FactConstantUniform& constant_uniform_fact) const;
+
+  // Yields the constant words associated with |constant_uniform_fact|.
+  std::vector<uint32_t> GetConstantWords(
+      const protobufs::FactConstantUniform& constant_uniform_fact) const;
+
+  // Yields the id of a constant of type |type_id| whose data matches the
+  // constant data in |constant_uniform_fact|, or 0 if no such constant is
+  // declared.
+  uint32_t GetConstantId(
+      opt::IRContext* context,
+      const protobufs::FactConstantUniform& constant_uniform_fact,
+      uint32_t type_id) const;
+
+  // Checks that the width of a floating-point constant is supported, and that
+  // the constant is finite.
+  bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact,
+                                    uint32_t width) const;
+
+  std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
+      facts_and_type_ids;
+};
+
+uint32_t FactManager::ConstantUniformFacts::GetConstantId(
+    opt::IRContext* context,
+    const protobufs::FactConstantUniform& constant_uniform_fact,
+    uint32_t type_id) const {
+  auto type = context->get_type_mgr()->GetType(type_id);
+  assert(type != nullptr && "Unknown type id.");
+  auto constant = context->get_constant_mgr()->GetConstant(
+      type, GetConstantWords(constant_uniform_fact));
+  return context->get_constant_mgr()->FindDeclaredConstant(constant, type_id);
+}
+
+std::vector<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords(
+    const protobufs::FactConstantUniform& constant_uniform_fact) const {
+  std::vector<uint32_t> result;
+  for (auto constant_word : constant_uniform_fact.constant_word()) {
+    result.push_back(constant_word);
+  }
+  return result;
+}
+
+bool FactManager::ConstantUniformFacts::DataMatches(
+    const opt::Instruction& constant_instruction,
+    const protobufs::FactConstantUniform& constant_uniform_fact) const {
+  assert(constant_instruction.opcode() == SpvOpConstant);
+  std::vector<uint32_t> data_in_constant;
+  for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
+    data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
+  }
+  return data_in_constant == GetConstantWords(constant_uniform_fact);
+}
+
+std::vector<uint32_t>
+FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
+    opt::IRContext* ir_context, uint32_t type_id) const {
+  std::vector<uint32_t> result;
+  std::set<uint32_t> already_seen;
+  for (auto& fact_and_type_id : facts_and_type_ids) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (auto constant_id =
+            GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
+      if (already_seen.find(constant_id) == already_seen.end()) {
+        result.push_back(constant_id);
+        already_seen.insert(constant_id);
+      }
+    }
+  }
+  return result;
+}
+
+const std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant(
+    opt::IRContext* ir_context, uint32_t constant_id) const {
+  std::vector<protobufs::UniformBufferElementDescriptor> result;
+  auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
+  assert(constant_inst->opcode() == SpvOpConstant &&
+         "The given id must be that of a constant");
+  auto type_id = constant_inst->type_id();
+  for (auto& fact_and_type_id : facts_and_type_ids) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (DataMatches(*constant_inst, fact_and_type_id.first)) {
+      result.emplace_back(
+          fact_and_type_id.first.uniform_buffer_element_descriptor());
+    }
+  }
+  return result;
+}
+
+uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor(
+    opt::IRContext* context,
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  // Consider each fact.
+  for (auto& fact_and_type : facts_and_type_ids) {
+    // Check whether the uniform descriptor associated with the fact matches
+    // |uniform_descriptor|.
+    if (UniformBufferElementDescriptorEquals()(
+            &uniform_descriptor,
+            &fact_and_type.first.uniform_buffer_element_descriptor())) {
+      return GetConstantId(context, fact_and_type.first, fact_and_type.second);
+    }
+  }
+  // No fact associated with the given uniform descriptor was found.
+  return 0;
+}
+
+std::vector<uint32_t>
+FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown()
+    const {
+  std::vector<uint32_t> result;
+  for (auto& fact_and_type : facts_and_type_ids) {
+    if (std::find(result.begin(), result.end(), fact_and_type.second) ==
+        result.end()) {
+      result.push_back(fact_and_type.second);
+    }
+  }
+  return result;
+}
+
+bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable(
+    const protobufs::FactConstantUniform& fact, uint32_t width) const {
+  const uint32_t kFloatWidth = 32;
+  const uint32_t kDoubleWidth = 64;
+  if (width != kFloatWidth && width != kDoubleWidth) {
+    // Only 32- and 64-bit floating-point types are handled.
+    return false;
+  }
+  std::vector<uint32_t> words = GetConstantWords(fact);
+  if (width == 32) {
+    float value;
+    memcpy(&value, words.data(), sizeof(float));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  } else {
+    double value;
+    memcpy(&value, words.data(), sizeof(double));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool FactManager::ConstantUniformFacts::AddFact(
+    const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
+  // Try to find a unique instruction that declares a variable such that the
+  // variable is decorated with the descriptor set and binding associated with
+  // the constant uniform fact.
+  opt::Instruction* uniform_variable = FindUniformVariable(
+      fact.uniform_buffer_element_descriptor(), context, true);
+
+  if (!uniform_variable) {
+    return false;
+  }
+
+  assert(SpvOpVariable == uniform_variable->opcode());
+  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
+
+  auto should_be_uniform_pointer_type =
+      context->get_type_mgr()->GetType(uniform_variable->type_id());
+  if (!should_be_uniform_pointer_type->AsPointer()) {
+    return false;
+  }
+  if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
+      SpvStorageClassUniform) {
+    return false;
+  }
+  auto should_be_uniform_pointer_instruction =
+      context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
+  auto element_type =
+      should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
+
+  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
+    auto should_be_composite_type =
+        context->get_def_use_mgr()->GetDef(element_type);
+    if (SpvOpTypeStruct == should_be_composite_type->opcode()) {
+      if (index >= should_be_composite_type->NumInOperands()) {
+        return false;
+      }
+      element_type = should_be_composite_type->GetSingleWordInOperand(index);
+    } else if (SpvOpTypeArray == should_be_composite_type->opcode()) {
+      auto array_length_constant =
+          context->get_constant_mgr()
+              ->GetConstantFromInst(context->get_def_use_mgr()->GetDef(
+                  should_be_composite_type->GetSingleWordInOperand(1)))
+              ->AsIntConstant();
+      if (array_length_constant->words().size() != 1) {
+        return false;
+      }
+      auto array_length = array_length_constant->GetU32();
+      if (index >= array_length) {
+        return false;
+      }
+      element_type = should_be_composite_type->GetSingleWordInOperand(0);
+    } else if (SpvOpTypeVector == should_be_composite_type->opcode()) {
+      auto vector_length = should_be_composite_type->GetSingleWordInOperand(1);
+      if (index >= vector_length) {
+        return false;
+      }
+      element_type = should_be_composite_type->GetSingleWordInOperand(0);
+    } else {
+      return false;
+    }
+  }
+  auto final_element_type = context->get_type_mgr()->GetType(element_type);
+  if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
+    return false;
+  }
+  auto width = final_element_type->AsFloat()
+                   ? final_element_type->AsFloat()->width()
+                   : final_element_type->AsInteger()->width();
+
+  if (final_element_type->AsFloat() &&
+      !FloatingPointValueIsSuitable(fact, width)) {
+    return false;
+  }
+
+  auto required_words = (width + 32 - 1) / 32;
+  if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
+    return false;
+  }
+  facts_and_type_ids.emplace_back(
+      std::pair<protobufs::FactConstantUniform, uint32_t>(fact, element_type));
+  return true;
+}
+
+FactManager::FactManager() {
+  uniform_constant_facts_ = MakeUnique<ConstantUniformFacts>();
+}
+
+FactManager::~FactManager() = default;
+
+void FactManager::AddFacts(const MessageConsumer& message_consumer,
+                           const protobufs::FactSequence& initial_facts,
+                           opt::IRContext* context) {
+  for (auto& fact : initial_facts.fact()) {
+    if (!AddFact(fact, context)) {
+      message_consumer(
+          SPV_MSG_WARNING, nullptr, {},
+          ("Invalid fact " + ToString(fact) + " ignored.").c_str());
+    }
+  }
+}
+
+bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact,
+                          spvtools::opt::IRContext* context) {
+  assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
+         "Right now this is the only fact.");
+  if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
+                                        context)) {
+    return false;
+  }
+  return true;
+}
+
+std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
+    opt::IRContext* ir_context, uint32_t type_id) const {
+  return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType(
+      ir_context, type_id);
+}
+
+const std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+                                              uint32_t constant_id) const {
+  return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context,
+                                                                   constant_id);
+}
+
+uint32_t FactManager::GetConstantFromUniformDescriptor(
+    opt::IRContext* context,
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  return uniform_constant_facts_->GetConstantFromUniformDescriptor(
+      context, uniform_descriptor);
+}
+
+std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
+    const {
+  return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown();
+}
+
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+FactManager::GetConstantUniformFactsAndTypes() const {
+  return uniform_constant_facts_->facts_and_type_ids;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h
new file mode 100644
index 0000000..cb4ac58
--- /dev/null
+++ b/source/fuzz/fact_manager.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_H_
+#define SOURCE_FUZZ_FACT_MANAGER_H_
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/constants.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Keeps track of facts about the module being transformed on which the fuzzing
+// process can depend. Some initial facts can be provided, for example about
+// guarantees on the values of inputs to SPIR-V entry points. Transformations
+// may then rely on these facts, can add further facts that they establish.
+// Facts are intended to be simple properties that either cannot be deduced from
+// the module (such as properties that are guaranteed to hold for entry point
+// inputs), or that are established by transformations, likely to be useful for
+// future transformations, and not completely trivial to deduce straight from
+// the module.
+class FactManager {
+ public:
+  FactManager();
+
+  ~FactManager();
+
+  // Adds all the facts from |facts|, checking them for validity with respect to
+  // |context|.  Warnings about invalid facts are communicated via
+  // |message_consumer|; such facts are otherwise ignored.
+  void AddFacts(const MessageConsumer& message_consumer,
+                const protobufs::FactSequence& facts, opt::IRContext* context);
+
+  // Checks the fact for validity with respect to |context|.  Returns false,
+  // with no side effects, if the fact is invalid.  Otherwise adds |fact| to the
+  // fact manager.
+  bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
+
+  // The fact manager will ultimately be responsible for managing a few distinct
+  // categories of facts. In principle there could be different fact managers
+  // for each kind of fact, but in practice providing one 'go to' place for
+  // facts will be convenient.  To keep some separation, the public methods of
+  // the fact manager should be grouped according to the kind of fact to which
+  // they relate.  At present we only have one kind of fact: facts about
+  // uniform variables.
+
+  //==============================
+  // Querying facts about uniform constants
+
+  // Provides the distinct type ids for which at least one  "constant ==
+  // uniform element" fact is known.
+  std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
+
+  // Provides distinct constant ids with type |type_id| for which at least one
+  // "constant == uniform element" fact is known.  If multiple identically-
+  // valued constants are relevant, only one will appear in the sequence.
+  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+      opt::IRContext* ir_context, uint32_t type_id) const;
+
+  // Provides details of all uniform elements that are known to be equal to the
+  // constant associated with |constant_id| in |ir_context|.
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+                                   uint32_t constant_id) const;
+
+  // Returns the id of a constant whose value is known to match that of
+  // |uniform_descriptor|, and whose type matches the type of the uniform
+  // element.  If multiple such constant is exist, the one that is returned
+  // is arbitrary.  Returns 0 if no such constant id exists.
+  uint32_t GetConstantFromUniformDescriptor(
+      opt::IRContext* context,
+      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+      const;
+
+  // Returns all "constant == uniform element" facts known to the fact
+  // manager, pairing each fact with id of the type that is associated with
+  // both the constant and the uniform element.
+  const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+  GetConstantUniformFactsAndTypes() const;
+
+  // End of uniform constant facts
+  //==============================
+
+ private:
+  // For each distinct kind of fact to be managed, we use a separate opaque
+  // struct type.
+
+  struct ConstantUniformFacts;  // Opaque struct for holding data about uniform
+                                // buffer elements.
+  std::unique_ptr<ConstantUniformFacts>
+      uniform_constant_facts_;  // Unique pointer to internal data.
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FACT_MANAGER_H_
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
new file mode 100644
index 0000000..89250a0
--- /dev/null
+++ b/source/fuzz/fuzzer.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer.h"
+
+#include <cassert>
+#include <sstream>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
+#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
+#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
+#include "source/fuzz/fuzzer_pass_permute_blocks.h"
+#include "source/fuzz/fuzzer_pass_split_blocks.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/opt/build_module.h"
+#include "source/spirv_fuzzer_options.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+const uint32_t kIdBoundGap = 100;
+}
+
+struct Fuzzer::Impl {
+  explicit Impl(spv_target_env env) : target_env(env) {}
+
+  const spv_target_env target_env;  // Target environment.
+  MessageConsumer consumer;         // Message consumer.
+};
+
+Fuzzer::Fuzzer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
+
+Fuzzer::~Fuzzer() = default;
+
+void Fuzzer::SetMessageConsumer(MessageConsumer c) {
+  impl_->consumer = std::move(c);
+}
+
+Fuzzer::FuzzerResultStatus Fuzzer::Run(
+    const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    spv_const_fuzzer_options options, std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out) const {
+  // Check compatibility between the library version being linked with and the
+  // header files being used.
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  tools.SetMessageConsumer(impl_->consumer);
+  if (!tools.IsValid()) {
+    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
+                    "Failed to create SPIRV-Tools interface; stopping.");
+    return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
+  }
+
+  // Initial binary should be valid.
+  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
+  }
+
+  // Build the module from the input binary.
+  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
+      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+  assert(ir_context);
+
+  // Make a PRNG, either from a given seed or from a random device.
+  PseudoRandomGenerator random_generator(
+      options->has_random_seed ? options->random_seed
+                               : static_cast<uint32_t>(std::random_device()()));
+
+  // The fuzzer will introduce new ids into the module.  The module's id bound
+  // gives the smallest id that can be used for this purpose.  We add an offset
+  // to this so that there is a sizeable gap between the ids used in the
+  // original module and the ids used for fuzzing, as a readability aid.
+  //
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
+  //  case where the maximum id bound is reached.
+  auto minimum_fresh_id = ir_context->module()->id_bound() + kIdBoundGap;
+  FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
+
+  FactManager fact_manager;
+  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+
+  // Add some essential ingredients to the module if they are not already
+  // present, such as boolean constants.
+  FuzzerPassAddUsefulConstructs(ir_context.get(), &fact_manager,
+                                &fuzzer_context, transformation_sequence_out)
+      .Apply();
+
+  // Apply some semantics-preserving passes.
+  FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
+                        transformation_sequence_out)
+      .Apply();
+  FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context,
+                          transformation_sequence_out)
+      .Apply();
+  FuzzerPassAddDeadContinues(ir_context.get(), &fact_manager, &fuzzer_context,
+                             transformation_sequence_out)
+      .Apply();
+  FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context,
+                               transformation_sequence_out)
+      .Apply();
+
+  // Finally, give the blocks in the module a good shake-up.
+  FuzzerPassPermuteBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
+                          transformation_sequence_out)
+      .Apply();
+
+  // Encode the module as a binary.
+  ir_context->module()->ToBinary(binary_out, false);
+
+  return Fuzzer::FuzzerResultStatus::kComplete;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
new file mode 100644
index 0000000..a257c2a
--- /dev/null
+++ b/source/fuzz/fuzzer.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_H_
+#define SOURCE_FUZZ_FUZZER_H_
+
+#include <memory>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// Transforms a SPIR-V module into a semantically equivalent SPIR-V module by
+// running a number of randomized fuzzer passes.
+class Fuzzer {
+ public:
+  // Possible statuses that can result from running the fuzzer.
+  enum class FuzzerResultStatus {
+    kComplete,
+    kFailedToCreateSpirvToolsInterface,
+    kInitialBinaryInvalid,
+  };
+
+  // Constructs a fuzzer from the given target environment.
+  explicit Fuzzer(spv_target_env env);
+
+  // Disables copy/move constructor/assignment operations.
+  Fuzzer(const Fuzzer&) = delete;
+  Fuzzer(Fuzzer&&) = delete;
+  Fuzzer& operator=(const Fuzzer&) = delete;
+  Fuzzer& operator=(Fuzzer&&) = delete;
+
+  ~Fuzzer();
+
+  // Sets the message consumer to the given |consumer|. The |consumer| will be
+  // invoked once for each message communicated from the library.
+  void SetMessageConsumer(MessageConsumer consumer);
+
+  // Transforms |binary_in| to |binary_out| by running a number of randomized
+  // fuzzer passes, controlled via |options|.  Initial facts about the input
+  // binary and the context in which it will execute are provided via
+  // |initial_facts|.  The transformation sequence that was applied is returned
+  // via |transformation_sequence_out|.
+  FuzzerResultStatus Run(
+      const std::vector<uint32_t>& binary_in,
+      const protobufs::FactSequence& initial_facts,
+      spv_const_fuzzer_options options, std::vector<uint32_t>* binary_out,
+      protobufs::TransformationSequence* transformation_sequence_out) const;
+
+ private:
+  struct Impl;                  // Opaque struct for holding internal data.
+  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_H_
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
new file mode 100644
index 0000000..c8e7719
--- /dev/null
+++ b/source/fuzz/fuzzer_context.cpp
@@ -0,0 +1,66 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_context.h"
+
+#include <cmath>
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+// Default probabilities for applying various transformations.
+// All values are percentages.
+// Keep them in alphabetical order.
+
+const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
+const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
+const uint32_t kDefaultChanceOfMovingBlockDown = 25;
+const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
+const uint32_t kDefaultChanceOfSplittingBlock = 20;
+
+// Default functions for controlling how deep to go during recursive
+// generation/transformation. Keep them in alphabetical order.
+
+const std::function<bool(uint32_t, RandomGenerator*)>
+    kDefaultGoDeeperInConstantObfuscation =
+        [](uint32_t current_depth, RandomGenerator* random_generator) -> bool {
+  double chance = 1.0 / std::pow(3.0, static_cast<float>(current_depth + 1));
+  return random_generator->RandomDouble() < chance;
+};
+
+}  // namespace
+
+FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
+                             uint32_t min_fresh_id)
+    : random_generator_(random_generator),
+      next_fresh_id_(min_fresh_id),
+      chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
+      chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue),
+      chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
+      chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
+      chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
+      go_deeper_in_constant_obfuscation_(
+          kDefaultGoDeeperInConstantObfuscation) {}
+
+FuzzerContext::~FuzzerContext() = default;
+
+uint32_t FuzzerContext::GetFreshId() { return next_fresh_id_++; }
+
+RandomGenerator* FuzzerContext::GetRandomGenerator() {
+  return random_generator_;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
new file mode 100644
index 0000000..3eaefc7
--- /dev/null
+++ b/source/fuzz/fuzzer_context.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_CONTEXT_H_
+#define SOURCE_FUZZ_FUZZER_CONTEXT_H_
+
+#include <functional>
+
+#include "source/fuzz/random_generator.h"
+#include "source/opt/function.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Encapsulates all parameters that control the fuzzing process, such as the
+// source of randomness and the probabilities with which transformations are
+// applied.
+class FuzzerContext {
+ public:
+  // Constructs a fuzzer context with a given random generator and the minimum
+  // value that can be used for fresh ids.
+  FuzzerContext(RandomGenerator* random_generator, uint32_t min_fresh_id);
+
+  ~FuzzerContext();
+
+  // Provides the random generator used to control fuzzing.
+  RandomGenerator* GetRandomGenerator();
+
+  // Yields an id that is guaranteed not to be used in the module being fuzzed,
+  // or to have been issued before.
+  uint32_t GetFreshId();
+
+  // Probabilities associated with applying various transformations.
+  // Keep them in alphabetical order.
+  uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
+  uint32_t GetChanceOfAddingDeadContinue() {
+    return chance_of_adding_dead_continue_;
+  }
+  uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
+  uint32_t GetChanceOfObfuscatingConstant() {
+    return chance_of_obfuscating_constant_;
+  }
+  uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+
+  // Probability distributions to control how deeply to recurse.
+  // Keep them in alphabetical order.
+  const std::function<bool(uint32_t, RandomGenerator*)>&
+  GoDeeperInConstantObfuscation() {
+    return go_deeper_in_constant_obfuscation_;
+  }
+
+ private:
+  // The source of randomness.
+  RandomGenerator* random_generator_;
+  // The next fresh id to be issued.
+  uint32_t next_fresh_id_;
+
+  // Probabilities associated with applying various transformations.
+  // Keep them in alphabetical order.
+  uint32_t chance_of_adding_dead_break_;
+  uint32_t chance_of_adding_dead_continue_;
+  uint32_t chance_of_moving_block_down_;
+  uint32_t chance_of_obfuscating_constant_;
+  uint32_t chance_of_splitting_block_;
+
+  // Functions to determine with what probability to go deeper when generating
+  // or mutating constructs recursively.
+  const std::function<bool(uint32_t, RandomGenerator*)>&
+      go_deeper_in_constant_obfuscation_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_CONTEXT_H_
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
new file mode 100644
index 0000000..823f2e0
--- /dev/null
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPass::FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager,
+                       FuzzerContext* fuzzer_context,
+                       protobufs::TransformationSequence* transformations)
+    : ir_context_(ir_context),
+      fact_manager_(fact_manager),
+      fuzzer_context_(fuzzer_context),
+      transformations_(transformations) {}
+
+FuzzerPass::~FuzzerPass() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
new file mode 100644
index 0000000..4d0861e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Interface for applying a pass of transformations to a module.
+class FuzzerPass {
+ public:
+  FuzzerPass(opt::IRContext* ir_context, FactManager* fact_manager,
+             FuzzerContext* fuzzer_context,
+             protobufs::TransformationSequence* transformations);
+
+  virtual ~FuzzerPass();
+
+  // Applies the pass to the module |ir_context_|, assuming and updating
+  // facts from |fact_manager_|, and using |fuzzer_context_| to guide the
+  // process.  Appends to |transformations_| all transformations that were
+  // applied during the pass.
+  virtual void Apply() = 0;
+
+ protected:
+  opt::IRContext* GetIRContext() const { return ir_context_; }
+
+  FactManager* GetFactManager() const { return fact_manager_; }
+
+  FuzzerContext* GetFuzzerContext() const { return fuzzer_context_; }
+
+  protobufs::TransformationSequence* GetTransformations() const {
+    return transformations_;
+  }
+
+ private:
+  opt::IRContext* ir_context_;
+  FactManager* fact_manager_;
+  FuzzerContext* fuzzer_context_;
+  protobufs::TransformationSequence* transformations_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_H_
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
new file mode 100644
index 0000000..72cd17b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
@@ -0,0 +1,97 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
+
+#include "source/fuzz/transformation_add_dead_break.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddDeadBreaks::FuzzerPassAddDeadBreaks(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddDeadBreaks::~FuzzerPassAddDeadBreaks() = default;
+
+void FuzzerPassAddDeadBreaks::Apply() {
+  // We first collect up lots of possibly-applicable transformations.
+  std::vector<TransformationAddDeadBreak> candidate_transformations;
+  // We consider each function separately.
+  for (auto& function : *GetIRContext()->module()) {
+    // For a given function, we find all the merge blocks in that function.
+    std::vector<uint32_t> merge_block_ids;
+    for (auto& block : function) {
+      auto maybe_merge_id = block.MergeBlockIdIfAny();
+      if (maybe_merge_id) {
+        merge_block_ids.push_back(maybe_merge_id);
+      }
+    }
+    // We rather aggressively consider the possibility of adding a break from
+    // every block in the function to every merge block.  Many of these will be
+    // inapplicable as they would be illegal.  That's OK - we later discard the
+    // ones that turn out to be no good.
+    for (auto& block : function) {
+      for (auto merge_block_id : merge_block_ids) {
+        // TODO(afd): right now we completely ignore OpPhi instructions at
+        //  merge blocks.  This will lead to interesting opportunities being
+        //  missed.
+        auto candidate_transformation = TransformationAddDeadBreak(
+            block.id(), merge_block_id,
+            GetFuzzerContext()->GetRandomGenerator()->RandomBool(), {});
+        if (candidate_transformation.IsApplicable(GetIRContext(),
+                                                  *GetFactManager())) {
+          // Only consider a transformation as a candidate if it is applicable.
+          candidate_transformations.push_back(
+              std::move(candidate_transformation));
+        }
+      }
+    }
+  }
+
+  // Go through the candidate transformations that were accumulated,
+  // probabilistically deciding whether to consider each one further and
+  // applying the still-applicable ones that are considered further.
+  //
+  // We iterate through the candidate transformations in a random order by
+  // repeatedly removing a random candidate transformation from the sequence
+  // until no candidate transformations remain.  This is done because
+  // transformations can potentially disable one another, so that iterating
+  // through them in order would lead to a higher probability of
+  // transformations appearing early in the sequence being applied compared
+  // with later transformations.
+  while (!candidate_transformations.empty()) {
+    // Choose a random index into the sequence of remaining candidate
+    // transformations.
+    auto index = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+        static_cast<uint32_t>(candidate_transformations.size()));
+    // Remove the transformation at the chosen index from the sequence.
+    auto transformation = std::move(candidate_transformations[index]);
+    candidate_transformations.erase(candidate_transformations.begin() + index);
+    // Probabilistically decide whether to try to apply it vs. ignore it, in the
+    // case that it is applicable.
+    if (transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+        GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+            GetFuzzerContext()->GetChanceOfAddingDeadBreak()) {
+      transformation.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() = transformation.ToMessage();
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.h b/source/fuzz/fuzzer_pass_add_dead_breaks.h
new file mode 100644
index 0000000..ad19856
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for adding dead break edges to the module.
+class FuzzerPassAddDeadBreaks : public FuzzerPass {
+ public:
+  FuzzerPassAddDeadBreaks(opt::IRContext* ir_context, FactManager* fact_manager,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddDeadBreaks();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BREAKS_H_
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
new file mode 100644
index 0000000..2156d36
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
@@ -0,0 +1,60 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
+
+#include "source/fuzz/transformation_add_dead_continue.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default;
+
+void FuzzerPassAddDeadContinues::Apply() {
+  // Consider every block in every function.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Make a transformation to add a dead continue from this node; if the
+      // node turns out to be inappropriate (e.g. by not being in a loop) the
+      // precondition for the transformation will fail and it will be ignored.
+      //
+      // TODO(afd): right now we completely ignore OpPhi instructions at
+      //  merge blocks.  This will lead to interesting opportunities being
+      //  missed.
+      auto candidate_transformation = TransformationAddDeadContinue(
+          block.id(), GetFuzzerContext()->GetRandomGenerator()->RandomBool(),
+          {});
+      // Probabilistically decide whether to apply the transformation in the
+      // case that it is applicable.
+      if (candidate_transformation.IsApplicable(GetIRContext(),
+                                                *GetFactManager()) &&
+          GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+              GetFuzzerContext()->GetChanceOfAddingDeadContinue()) {
+        candidate_transformation.Apply(GetIRContext(), GetFactManager());
+        *GetTransformations()->add_transformation() =
+            candidate_transformation.ToMessage();
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.h b/source/fuzz/fuzzer_pass_add_dead_continues.h
new file mode 100644
index 0000000..6cadc97
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for adding dead continue edges to the module.
+class FuzzerPassAddDeadContinues : public FuzzerPass {
+ public:
+  FuzzerPassAddDeadContinues(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddDeadContinues();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
new file mode 100644
index 0000000..6ac4ae9
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+
+#include "source/fuzz/transformation_add_constant_boolean.h"
+#include "source/fuzz/transformation_add_constant_scalar.h"
+#include "source/fuzz/transformation_add_type_boolean.h"
+#include "source/fuzz/transformation_add_type_float.h"
+#include "source/fuzz/transformation_add_type_int.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+using opt::IRContext;
+
+FuzzerPassAddUsefulConstructs::FuzzerPassAddUsefulConstructs(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+
+FuzzerPassAddUsefulConstructs::~FuzzerPassAddUsefulConstructs() = default;
+
+void FuzzerPassAddUsefulConstructs::MaybeAddIntConstant(
+    uint32_t width, bool is_signed, std::vector<uint32_t> data) const {
+  opt::analysis::Integer temp_int_type(width, is_signed);
+  assert(GetIRContext()->get_type_mgr()->GetId(&temp_int_type) &&
+         "int type should already be registered.");
+  auto registered_int_type = GetIRContext()
+                                 ->get_type_mgr()
+                                 ->GetRegisteredType(&temp_int_type)
+                                 ->AsInteger();
+  auto int_type_id = GetIRContext()->get_type_mgr()->GetId(registered_int_type);
+  assert(int_type_id &&
+         "The relevant int type should have been added to the module already.");
+  opt::analysis::IntConstant int_constant(registered_int_type, data);
+  if (!GetIRContext()->get_constant_mgr()->FindConstant(&int_constant)) {
+    TransformationAddConstantScalar add_constant_int =
+        TransformationAddConstantScalar(GetFuzzerContext()->GetFreshId(),
+                                        int_type_id, data);
+    assert(add_constant_int.IsApplicable(GetIRContext(), *GetFactManager()) &&
+           "Should be applicable by construction.");
+    add_constant_int.Apply(GetIRContext(), GetFactManager());
+    *GetTransformations()->add_transformation() = add_constant_int.ToMessage();
+  }
+}
+
+void FuzzerPassAddUsefulConstructs::MaybeAddFloatConstant(
+    uint32_t width, std::vector<uint32_t> data) const {
+  opt::analysis::Float temp_float_type(width);
+  assert(GetIRContext()->get_type_mgr()->GetId(&temp_float_type) &&
+         "float type should already be registered.");
+  auto registered_float_type = GetIRContext()
+                                   ->get_type_mgr()
+                                   ->GetRegisteredType(&temp_float_type)
+                                   ->AsFloat();
+  auto float_type_id =
+      GetIRContext()->get_type_mgr()->GetId(registered_float_type);
+  assert(
+      float_type_id &&
+      "The relevant float type should have been added to the module already.");
+  opt::analysis::FloatConstant float_constant(registered_float_type, data);
+  if (!GetIRContext()->get_constant_mgr()->FindConstant(&float_constant)) {
+    TransformationAddConstantScalar add_constant_float =
+        TransformationAddConstantScalar(GetFuzzerContext()->GetFreshId(),
+                                        float_type_id, data);
+    assert(add_constant_float.IsApplicable(GetIRContext(), *GetFactManager()) &&
+           "Should be applicable by construction.");
+    add_constant_float.Apply(GetIRContext(), GetFactManager());
+    *GetTransformations()->add_transformation() =
+        add_constant_float.ToMessage();
+  }
+}
+
+void FuzzerPassAddUsefulConstructs::Apply() {
+  {
+    // Add boolean type if not present.
+    opt::analysis::Bool temp_bool_type;
+    if (!GetIRContext()->get_type_mgr()->GetId(&temp_bool_type)) {
+      auto add_type_boolean =
+          TransformationAddTypeBoolean(GetFuzzerContext()->GetFreshId());
+      assert(add_type_boolean.IsApplicable(GetIRContext(), *GetFactManager()) &&
+             "Should be applicable by construction.");
+      add_type_boolean.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() =
+          add_type_boolean.ToMessage();
+    }
+  }
+
+  {
+    // Add signed and unsigned 32-bit integer types if not present.
+    for (auto is_signed : {true, false}) {
+      opt::analysis::Integer temp_int_type(32, is_signed);
+      if (!GetIRContext()->get_type_mgr()->GetId(&temp_int_type)) {
+        TransformationAddTypeInt add_type_int = TransformationAddTypeInt(
+            GetFuzzerContext()->GetFreshId(), 32, is_signed);
+        assert(add_type_int.IsApplicable(GetIRContext(), *GetFactManager()) &&
+               "Should be applicable by construction.");
+        add_type_int.Apply(GetIRContext(), GetFactManager());
+        *GetTransformations()->add_transformation() = add_type_int.ToMessage();
+      }
+    }
+  }
+
+  {
+    // Add 32-bit float type if not present.
+    opt::analysis::Float temp_float_type(32);
+    if (!GetIRContext()->get_type_mgr()->GetId(&temp_float_type)) {
+      TransformationAddTypeFloat add_type_float =
+          TransformationAddTypeFloat(GetFuzzerContext()->GetFreshId(), 32);
+      assert(add_type_float.IsApplicable(GetIRContext(), *GetFactManager()) &&
+             "Should be applicable by construction.");
+      add_type_float.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() = add_type_float.ToMessage();
+    }
+  }
+
+  // Add boolean constants true and false if not present.
+  opt::analysis::Bool temp_bool_type;
+  auto bool_type = GetIRContext()
+                       ->get_type_mgr()
+                       ->GetRegisteredType(&temp_bool_type)
+                       ->AsBool();
+  for (auto boolean_value : {true, false}) {
+    // Add OpConstantTrue/False if not already there.
+    opt::analysis::BoolConstant bool_constant(bool_type, boolean_value);
+    if (!GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant)) {
+      TransformationAddConstantBoolean add_constant_boolean(
+          GetFuzzerContext()->GetFreshId(), boolean_value);
+      assert(add_constant_boolean.IsApplicable(GetIRContext(),
+                                               *GetFactManager()) &&
+             "Should be applicable by construction.");
+      add_constant_boolean.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() =
+          add_constant_boolean.ToMessage();
+    }
+  }
+
+  // Add signed and unsigned 32-bit integer constants 0 and 1 if not present.
+  for (auto is_signed : {true, false}) {
+    for (auto value : {0u, 1u}) {
+      MaybeAddIntConstant(32, is_signed, {value});
+    }
+  }
+
+  // Add 32-bit float constants 0.0 and 1.0 if not present.
+  uint32_t uint_data[2];
+  float float_data[2] = {0.0, 1.0};
+  memcpy(uint_data, float_data, sizeof(float_data));
+  for (unsigned int& datum : uint_data) {
+    MaybeAddFloatConstant(32, {datum});
+  }
+
+  // For every known-to-be-constant uniform, make sure we have instructions
+  // declaring:
+  // - a pointer type with uniform storage class, whose pointee type is the type
+  //   of the element
+  // - a signed integer constant for each index required to access the element
+  // - a constant for the constant value itself
+  for (auto& fact_and_type_id :
+       GetFactManager()->GetConstantUniformFactsAndTypes()) {
+    uint32_t element_type_id = fact_and_type_id.second;
+    assert(element_type_id);
+    auto element_type =
+        GetIRContext()->get_type_mgr()->GetType(element_type_id);
+    assert(element_type &&
+           "If the constant uniform fact is well-formed, the module must "
+           "already have a declaration of the type for the uniform element.");
+    opt::analysis::Pointer uniform_pointer(element_type,
+                                           SpvStorageClassUniform);
+    if (!GetIRContext()->get_type_mgr()->GetId(&uniform_pointer)) {
+      auto add_pointer =
+          TransformationAddTypePointer(GetFuzzerContext()->GetFreshId(),
+                                       SpvStorageClassUniform, element_type_id);
+      assert(add_pointer.IsApplicable(GetIRContext(), *GetFactManager()) &&
+             "Should be applicable by construction.");
+      add_pointer.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() = add_pointer.ToMessage();
+    }
+    std::vector<uint32_t> words;
+    for (auto word : fact_and_type_id.first.constant_word()) {
+      words.push_back(word);
+    }
+    // We get the element type again as the type manager may have been
+    // invalidated since we last retrieved it.
+    element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
+    if (element_type->AsInteger()) {
+      MaybeAddIntConstant(element_type->AsInteger()->width(),
+                          element_type->AsInteger()->IsSigned(), words);
+    } else {
+      assert(element_type->AsFloat() &&
+             "Known uniform values must be integer or floating-point.");
+      MaybeAddFloatConstant(element_type->AsFloat()->width(), words);
+    }
+    for (auto index :
+         fact_and_type_id.first.uniform_buffer_element_descriptor().index()) {
+      MaybeAddIntConstant(32, true, {index});
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.h b/source/fuzz/fuzzer_pass_add_useful_constructs.h
new file mode 100644
index 0000000..a8ac9a3
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_USEFUL_CONSTRUCTS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_USEFUL_CONSTRUCTS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// An initial pass for adding useful ingredients to the module, such as boolean
+// constants, if they are not present.
+class FuzzerPassAddUsefulConstructs : public FuzzerPass {
+ public:
+  FuzzerPassAddUsefulConstructs(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddUsefulConstructs() override;
+
+  void Apply() override;
+
+ private:
+  void MaybeAddIntConstant(uint32_t width, bool is_signed,
+                           std::vector<uint32_t> data) const;
+
+  void MaybeAddFloatConstant(uint32_t width, std::vector<uint32_t> data) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_ADD_USEFUL_CONSTRUCTS_
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
new file mode 100644
index 0000000..ff52ea9
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
@@ -0,0 +1,462 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
+
+#include <cmath>
+
+#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassObfuscateConstants::~FuzzerPassObfuscateConstants() = default;
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
+    uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+    const std::vector<SpvOp>& greater_than_opcodes,
+    const std::vector<SpvOp>& less_than_opcodes, uint32_t constant_id_1,
+    uint32_t constant_id_2, bool first_constant_is_larger) {
+  auto bool_constant_opcode = GetIRContext()
+                                  ->get_def_use_mgr()
+                                  ->GetDef(bool_constant_use.id_of_interest())
+                                  ->opcode();
+  assert((bool_constant_opcode == SpvOpConstantFalse ||
+          bool_constant_opcode == SpvOpConstantTrue) &&
+         "Precondition: this must be a usage of a boolean constant.");
+
+  // Pick an opcode at random.  First randomly decide whether to generate
+  // a 'greater than' or 'less than' kind of opcode, and then select a
+  // random opcode from the resulting subset.
+  SpvOp comparison_opcode;
+  if (GetFuzzerContext()->GetRandomGenerator()->RandomBool()) {
+    comparison_opcode = greater_than_opcodes
+        [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+            static_cast<uint32_t>(greater_than_opcodes.size()))];
+  } else {
+    comparison_opcode = less_than_opcodes
+        [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+            static_cast<uint32_t>(less_than_opcodes.size()))];
+  }
+
+  // We now need to decide how to order constant_id_1 and constant_id_2 such
+  // that 'constant_id_1 comparison_opcode constant_id_2' evaluates to the
+  // boolean constant.
+  const bool is_greater_than_opcode =
+      std::find(greater_than_opcodes.begin(), greater_than_opcodes.end(),
+                comparison_opcode) != greater_than_opcodes.end();
+  uint32_t lhs_id;
+  uint32_t rhs_id;
+  if ((bool_constant_opcode == SpvOpConstantTrue &&
+       first_constant_is_larger == is_greater_than_opcode) ||
+      (bool_constant_opcode == SpvOpConstantFalse &&
+       first_constant_is_larger != is_greater_than_opcode)) {
+    lhs_id = constant_id_1;
+    rhs_id = constant_id_2;
+  } else {
+    lhs_id = constant_id_2;
+    rhs_id = constant_id_1;
+  }
+
+  // We can now make a transformation that will replace |bool_constant_use|
+  // with an expression of the form (written using infix notation):
+  // |lhs_id| |comparison_opcode| |rhs_id|
+  auto transformation = TransformationReplaceBooleanConstantWithConstantBinary(
+      bool_constant_use, lhs_id, rhs_id, comparison_opcode,
+      GetFuzzerContext()->GetFreshId());
+  // The transformation should be applicable by construction.
+  assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()));
+
+  // Applying this transformation yields a pointer to the new instruction that
+  // computes the result of the binary expression.
+  auto binary_operator_instruction =
+      transformation.ApplyWithResult(GetIRContext(), GetFactManager());
+
+  // Add this transformation to the sequence of transformations that have been
+  // applied.
+  *GetTransformations()->add_transformation() = transformation.ToMessage();
+
+  // Having made a binary expression, there may now be opportunities to further
+  // obfuscate the constants used as the LHS and RHS of the expression (e.g. by
+  // replacing them with loads from known uniforms).
+  //
+  // We thus consider operands 0 and 1 (LHS and RHS in turn).
+  for (uint32_t index : {0u, 1u}) {
+    // We randomly decide, based on the current depth of obfuscation, whether
+    // to further obfuscate this operand.
+    if (GetFuzzerContext()->GoDeeperInConstantObfuscation()(
+            depth, GetFuzzerContext()->GetRandomGenerator())) {
+      auto in_operand_use = transformation::MakeIdUseDescriptor(
+          binary_operator_instruction->GetSingleWordInOperand(index),
+          binary_operator_instruction->opcode(), index,
+          binary_operator_instruction->result_id(), 0);
+      ObfuscateConstant(depth + 1, in_operand_use);
+    }
+  }
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaFloatConstantPair(
+    uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+    uint32_t float_constant_id_1, uint32_t float_constant_id_2) {
+  auto float_constant_1 = GetIRContext()
+                              ->get_constant_mgr()
+                              ->FindDeclaredConstant(float_constant_id_1)
+                              ->AsFloatConstant();
+  auto float_constant_2 = GetIRContext()
+                              ->get_constant_mgr()
+                              ->FindDeclaredConstant(float_constant_id_2)
+                              ->AsFloatConstant();
+  assert(float_constant_1->words() != float_constant_2->words() &&
+         "The constants should not be identical.");
+  assert(std::isfinite(float_constant_1->GetValueAsDouble()) &&
+         "The constants must be finite numbers.");
+  assert(std::isfinite(float_constant_2->GetValueAsDouble()) &&
+         "The constants must be finite numbers.");
+  bool first_constant_is_larger;
+  assert(float_constant_1->type()->AsFloat()->width() ==
+             float_constant_2->type()->AsFloat()->width() &&
+         "First and second floating-point constants must have the same width.");
+  if (float_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        float_constant_1->GetFloat() > float_constant_2->GetFloat();
+  } else {
+    assert(float_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported floating-point widths are 32 and 64.");
+    first_constant_is_larger =
+        float_constant_1->GetDouble() > float_constant_2->GetDouble();
+  }
+  std::vector<SpvOp> greater_than_opcodes{
+      SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
+      SpvOpFUnordGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{
+      SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
+      SpvOpFUnordGreaterThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      float_constant_id_1, float_constant_id_2, first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::
+    ObfuscateBoolConstantViaSignedIntConstantPair(
+        uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+        uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2) {
+  auto signed_int_constant_1 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(signed_int_constant_id_1)
+          ->AsIntConstant();
+  auto signed_int_constant_2 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(signed_int_constant_id_2)
+          ->AsIntConstant();
+  assert(signed_int_constant_1->words() != signed_int_constant_2->words() &&
+         "The constants should not be identical.");
+  bool first_constant_is_larger;
+  assert(signed_int_constant_1->type()->AsInteger()->width() ==
+             signed_int_constant_2->type()->AsInteger()->width() &&
+         "First and second floating-point constants must have the same width.");
+  assert(signed_int_constant_1->type()->AsInteger()->IsSigned());
+  assert(signed_int_constant_2->type()->AsInteger()->IsSigned());
+  if (signed_int_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        signed_int_constant_1->GetS32() > signed_int_constant_2->GetS32();
+  } else {
+    assert(signed_int_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported integer widths are 32 and 64.");
+    first_constant_is_larger =
+        signed_int_constant_1->GetS64() > signed_int_constant_2->GetS64();
+  }
+  std::vector<SpvOp> greater_than_opcodes{SpvOpSGreaterThan,
+                                          SpvOpSGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{SpvOpSLessThan, SpvOpSLessThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      signed_int_constant_id_1, signed_int_constant_id_2,
+      first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::
+    ObfuscateBoolConstantViaUnsignedIntConstantPair(
+        uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+        uint32_t unsigned_int_constant_id_1,
+        uint32_t unsigned_int_constant_id_2) {
+  auto unsigned_int_constant_1 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(unsigned_int_constant_id_1)
+          ->AsIntConstant();
+  auto unsigned_int_constant_2 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(unsigned_int_constant_id_2)
+          ->AsIntConstant();
+  assert(unsigned_int_constant_1->words() != unsigned_int_constant_2->words() &&
+         "The constants should not be identical.");
+  bool first_constant_is_larger;
+  assert(unsigned_int_constant_1->type()->AsInteger()->width() ==
+             unsigned_int_constant_2->type()->AsInteger()->width() &&
+         "First and second floating-point constants must have the same width.");
+  assert(!unsigned_int_constant_1->type()->AsInteger()->IsSigned());
+  assert(!unsigned_int_constant_2->type()->AsInteger()->IsSigned());
+  if (unsigned_int_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        unsigned_int_constant_1->GetU32() > unsigned_int_constant_2->GetU32();
+  } else {
+    assert(unsigned_int_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported integer widths are 32 and 64.");
+    first_constant_is_larger =
+        unsigned_int_constant_1->GetU64() > unsigned_int_constant_2->GetU64();
+  }
+  std::vector<SpvOp> greater_than_opcodes{SpvOpUGreaterThan,
+                                          SpvOpUGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{SpvOpULessThan, SpvOpULessThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      unsigned_int_constant_id_1, unsigned_int_constant_id_2,
+      first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
+    uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
+  // We want to replace the boolean constant use with a binary expression over
+  // scalar constants, but only if we can then potentially replace the constants
+  // with uniforms of the same value.
+
+  auto available_types_with_uniforms =
+      GetFactManager()->GetTypesForWhichUniformValuesAreKnown();
+  if (available_types_with_uniforms.empty()) {
+    // Do not try to obfuscate if we do not have access to any uniform
+    // elements with known values.
+    return;
+  }
+  auto chosen_type_id = available_types_with_uniforms
+      [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+          static_cast<uint32_t>(available_types_with_uniforms.size()))];
+  auto available_constants =
+      GetFactManager()->GetConstantsAvailableFromUniformsForType(
+          GetIRContext(), chosen_type_id);
+  if (available_constants.size() == 1) {
+    // TODO(afd): for now we only obfuscate a boolean if there are at least
+    //  two constants available from uniforms, so that we can do a
+    //  comparison between them. It would be good to be able to do the
+    //  obfuscation even if there is only one such constant, if there is
+    //  also another regular constant available.
+    return;
+  }
+
+  // We know we have at least two known-to-be-constant uniforms of the chosen
+  // type.  Pick one of them at random.
+  auto constant_index_1 =
+      GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+          static_cast<uint32_t>(available_constants.size()));
+  uint32_t constant_index_2;
+
+  // Now choose another one distinct from the first one.
+  do {
+    constant_index_2 = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+        static_cast<uint32_t>(available_constants.size()));
+  } while (constant_index_1 == constant_index_2);
+
+  auto constant_id_1 = available_constants[constant_index_1];
+  auto constant_id_2 = available_constants[constant_index_2];
+
+  assert(constant_id_1 != 0 && constant_id_2 != 0 &&
+         "We should not find an available constant with an id of 0.");
+
+  // Now perform the obfuscation, according to whether the type of the constants
+  // is float, signed int, or unsigned int.
+  auto chosen_type = GetIRContext()->get_type_mgr()->GetType(chosen_type_id);
+  if (chosen_type->AsFloat()) {
+    ObfuscateBoolConstantViaFloatConstantPair(depth, constant_use,
+                                              constant_id_1, constant_id_2);
+  } else {
+    assert(chosen_type->AsInteger() &&
+           "We should only have uniform facts about ints and floats.");
+    if (chosen_type->AsInteger()->IsSigned()) {
+      ObfuscateBoolConstantViaSignedIntConstantPair(
+          depth, constant_use, constant_id_1, constant_id_2);
+    } else {
+      ObfuscateBoolConstantViaUnsignedIntConstantPair(
+          depth, constant_use, constant_id_1, constant_id_2);
+    }
+  }
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateScalarConstant(
+    uint32_t /*depth*/, const protobufs::IdUseDescriptor& constant_use) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2670): consider
+  //  additional ways to obfuscate scalar constants.
+
+  // Check whether we know that any uniforms are guaranteed to be equal to the
+  // scalar constant associated with |constant_use|.
+  auto uniform_descriptors = GetFactManager()->GetUniformDescriptorsForConstant(
+      GetIRContext(), constant_use.id_of_interest());
+  if (uniform_descriptors.empty()) {
+    // No relevant uniforms, so do not obfuscate.
+    return;
+  }
+
+  // Choose a random available uniform known to be equal to the constant.
+  protobufs::UniformBufferElementDescriptor uniform_descriptor =
+      uniform_descriptors
+          [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+              static_cast<uint32_t>(uniform_descriptors.size()))];
+  // Create, apply and record a transformation to replace the constant use with
+  // the result of a load from the chosen uniform.
+  auto transformation = TransformationReplaceConstantWithUniform(
+      constant_use, uniform_descriptor, GetFuzzerContext()->GetFreshId(),
+      GetFuzzerContext()->GetFreshId());
+  // Transformation should be applicable by construction.
+  assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()));
+  transformation.Apply(GetIRContext(), GetFactManager());
+  *GetTransformations()->add_transformation() = transformation.ToMessage();
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateConstant(
+    uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
+  switch (GetIRContext()
+              ->get_def_use_mgr()
+              ->GetDef(constant_use.id_of_interest())
+              ->opcode()) {
+    case SpvOpConstantTrue:
+    case SpvOpConstantFalse:
+      ObfuscateBoolConstant(depth, constant_use);
+      break;
+    case SpvOpConstant:
+      ObfuscateScalarConstant(depth, constant_use);
+      break;
+    default:
+      assert(false && "The opcode should be one of the above.");
+      break;
+  }
+}
+
+void FuzzerPassObfuscateConstants::MaybeAddConstantIdUse(
+    const opt::Instruction& inst, uint32_t in_operand_index,
+    uint32_t base_instruction_result_id,
+    const std::map<SpvOp, uint32_t>& skipped_opcode_count,
+    std::vector<protobufs::IdUseDescriptor>* constant_uses) {
+  if (inst.GetInOperand(in_operand_index).type != SPV_OPERAND_TYPE_ID) {
+    // The operand is not an id, so it cannot be a constant id.
+    return;
+  }
+  auto operand_id = inst.GetSingleWordInOperand(in_operand_index);
+  auto operand_definition =
+      GetIRContext()->get_def_use_mgr()->GetDef(operand_id);
+  switch (operand_definition->opcode()) {
+    case SpvOpConstantFalse:
+    case SpvOpConstantTrue:
+    case SpvOpConstant: {
+      // The operand is a constant id, so make an id use descriptor and record
+      // it.
+      protobufs::IdUseDescriptor id_use_descriptor;
+      id_use_descriptor.set_id_of_interest(operand_id);
+      id_use_descriptor.set_target_instruction_opcode(inst.opcode());
+      id_use_descriptor.set_in_operand_index(in_operand_index);
+      id_use_descriptor.set_base_instruction_result_id(
+          base_instruction_result_id);
+      id_use_descriptor.set_num_opcodes_to_ignore(
+          skipped_opcode_count.find(inst.opcode()) == skipped_opcode_count.end()
+              ? 0
+              : skipped_opcode_count.at(inst.opcode()));
+      constant_uses->push_back(id_use_descriptor);
+    } break;
+    default:
+      break;
+  }
+}
+
+void FuzzerPassObfuscateConstants::Apply() {
+  // First, gather up all the constant uses available in the module, by going
+  // through each block in each function.
+  std::vector<protobufs::IdUseDescriptor> constant_uses;
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // For each constant use we encounter we are going to make an id use
+      // descriptor. An id use is described with respect to a base instruction;
+      // if there are instructions at the start of the block without result ids,
+      // the base instruction will have to be the block's label.
+      uint32_t base_instruction_result_id = block.id();
+
+      // An id use descriptor also records how many instructions of a particular
+      // opcode need to be skipped in order to find the instruction of interest
+      // from the base instruction. We maintain a mapping that records a skip
+      // count for each relevant opcode.
+      std::map<SpvOp, uint32_t> skipped_opcode_count;
+
+      // Go through each instruction in the block.
+      for (auto& inst : block) {
+        if (inst.HasResultId()) {
+          // The instruction has a result id, so can be used as the base
+          // instruction from now on, until another instruction with a result id
+          // is encountered.
+          base_instruction_result_id = inst.result_id();
+          // Opcode skip counts were with respect to the previous base
+          // instruction and are now irrelevant.
+          skipped_opcode_count.clear();
+        }
+
+        // Consider each operand of the instruction, and add a constant id use
+        // for the operand if relevant.
+        for (uint32_t in_operand_index = 0;
+             in_operand_index < inst.NumInOperands(); in_operand_index++) {
+          MaybeAddConstantIdUse(inst, in_operand_index,
+                                base_instruction_result_id,
+                                skipped_opcode_count, &constant_uses);
+        }
+
+        if (!inst.HasResultId()) {
+          // The instruction has no result id, so in order to identify future id
+          // uses for instructions with this opcode from the existing base
+          // instruction, we need to increase the skip count for this opcode.
+          skipped_opcode_count[inst.opcode()] =
+              skipped_opcode_count.find(inst.opcode()) ==
+                      skipped_opcode_count.end()
+                  ? 1
+                  : skipped_opcode_count[inst.opcode()] + 1;
+        }
+      }
+    }
+  }
+
+  // Go through the constant uses in a random order by repeatedly pulling out a
+  // constant use at a random index.
+  while (!constant_uses.empty()) {
+    auto index = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+        static_cast<uint32_t>(constant_uses.size()));
+    auto constant_use = std::move(constant_uses[index]);
+    constant_uses.erase(constant_uses.begin() + index);
+    // Decide probabilistically whether to skip or obfuscate this constant use.
+    if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+        GetFuzzerContext()->GetChanceOfObfuscatingConstant()) {
+      continue;
+    }
+    ObfuscateConstant(0, constant_use);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h
new file mode 100644
index 0000000..03477a5
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for turning uses of constants into more complex forms.
+// Examples include replacing 'true' with '42 < 52', and replacing '42' with
+// 'a.b.c' if 'a.b.c' is known to hold the value '42'.
+class FuzzerPassObfuscateConstants : public FuzzerPass {
+ public:
+  FuzzerPassObfuscateConstants(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassObfuscateConstants() override;
+
+  void Apply() override;
+
+ private:
+  // Applies 0 or more transformations to potentially obfuscate the constant
+  // use represented by |constant_use|.  The |depth| parameter controls how
+  // deeply obfuscation can recurse.
+  void ObfuscateConstant(uint32_t depth,
+                         const protobufs::IdUseDescriptor& constant_use);
+
+  // This method will try to turn |constant_use|, required to be a use of a
+  // boolean constant, into a binary expression on scalar constants, which may
+  // themselves be recursively obfuscated.
+  void ObfuscateBoolConstant(uint32_t depth,
+                             const protobufs::IdUseDescriptor& constant_use);
+
+  // This method will try to turn |constant_use|, required to be a use of a
+  // scalar constant, into the value loaded from a uniform known to have the
+  // same value as the constant (if one exists).
+  void ObfuscateScalarConstant(uint32_t depth,
+                               const protobufs::IdUseDescriptor& constant_use);
+
+  // Applies a transformation to replace the boolean constant usage represented
+  // by |bool_constant_use| with a binary expression involving
+  // |float_constant_id_1| and |float_constant_id_2|, which must not be equal
+  // to one another.  Possibly further obfuscates the uses of these float
+  // constants.  The |depth| parameter controls how deeply obfuscation can
+  // recurse.
+  void ObfuscateBoolConstantViaFloatConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t float_constant_id_1, uint32_t float_constant_id_2);
+
+  // Similar to the above, but for signed int constants.
+  void ObfuscateBoolConstantViaSignedIntConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2);
+
+  // Similar to the above, but for unsigned int constants.
+  void ObfuscateBoolConstantViaUnsignedIntConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t unsigned_int_constant_id_1, uint32_t unsigned_int_constant_id_2);
+
+  // A helper method to capture the common parts of the above methods.
+  // The method is used to obfuscate the boolean constant usage represented by
+  // |bool_constant_use| by replacing it with '|constant_id_1| OP
+  // |constant_id_2|', where 'OP' is chosen from either |greater_than_opcodes|
+  // or |less_than_opcodes|.
+  //
+  // The two constant ids must not represent the same value, and thus
+  // |greater_than_opcodes| may include 'greater than or equal' opcodes
+  // (similar for |less_than_opcodes|).
+  void ObfuscateBoolConstantViaConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      const std::vector<SpvOp>& greater_than_opcodes,
+      const std::vector<SpvOp>& less_than_opcodes, uint32_t constant_id_1,
+      uint32_t constant_id_2, bool first_constant_is_larger);
+
+  // A helper method to determine whether input operand |in_operand_index| of
+  // |inst| is the id of a constant, and add an id use descriptor to
+  // |candidate_constant_uses| if so.  The other parameters are used for id use
+  // descriptor construction.
+  void MaybeAddConstantIdUse(
+      const opt::Instruction& inst, uint32_t in_operand_index,
+      uint32_t base_instruction_result_id,
+      const std::map<SpvOp, uint32_t>& skipped_opcode_count,
+      std::vector<protobufs::IdUseDescriptor>* constant_uses);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.cpp b/source/fuzz/fuzzer_pass_permute_blocks.cpp
new file mode 100644
index 0000000..7ab2ee3
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_blocks.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_permute_blocks.h"
+
+#include "source/fuzz/transformation_move_block_down.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteBlocks::FuzzerPassPermuteBlocks(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassPermuteBlocks::~FuzzerPassPermuteBlocks() = default;
+
+void FuzzerPassPermuteBlocks::Apply() {
+  // For now we do something very simple: we randomly decide whether to move a
+  // block, and for each block that we do move, we push it down as far as we
+  // legally can.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2635): it would be
+  //  nice to randomly sample from the set of legal block permutations and then
+  //  encode the chosen permutation via a series of move-block-down
+  //  transformations.  This should be possible but will require some thought.
+
+  for (auto& function : *GetIRContext()->module()) {
+    std::vector<uint32_t> block_ids;
+    // Collect all block ids for the function before messing with block
+    // ordering.
+    for (auto& block : function) {
+      block_ids.push_back(block.id());
+    }
+    // Now consider each block id.  We consider block ids in reverse, because
+    // e.g. in code generated from the following:
+    //
+    // if (...) {
+    //   A
+    //   B
+    // } else {
+    //   C
+    // }
+    //
+    // block A cannot be moved down, but B has freedom to move and that movement
+    // would provide more freedom for A to move.
+    for (auto id = block_ids.rbegin(); id != block_ids.rend(); ++id) {
+      // Randomly decide whether to ignore the block id.
+      if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+          GetFuzzerContext()->GetChanceOfMovingBlockDown()) {
+        continue;
+      }
+      // Keep pushing the block down, until pushing down fails.
+      // The loop is guaranteed to terminate because a block cannot be pushed
+      // down indefinitely.
+      while (true) {
+        TransformationMoveBlockDown transformation(*id);
+        if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
+          transformation.Apply(GetIRContext(), GetFactManager());
+          *GetTransformations()->add_transformation() =
+              transformation.ToMessage();
+        } else {
+          break;
+        }
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h
new file mode 100644
index 0000000..d8aed72
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_blocks.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for shuffling the blocks of the module in a validity-preserving
+// manner.
+class FuzzerPassPermuteBlocks : public FuzzerPass {
+ public:
+  FuzzerPassPermuteBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermuteBlocks() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
diff --git a/source/fuzz/fuzzer_pass_split_blocks.cpp b/source/fuzz/fuzzer_pass_split_blocks.cpp
new file mode 100644
index 0000000..39f84ec
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_split_blocks.cpp
@@ -0,0 +1,97 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_split_blocks.h"
+
+#include <utility>
+#include <vector>
+
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassSplitBlocks::FuzzerPassSplitBlocks(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassSplitBlocks::~FuzzerPassSplitBlocks() = default;
+
+void FuzzerPassSplitBlocks::Apply() {
+  // Gather up pointers to all the blocks in the module.  We are then able to
+  // iterate over these pointers and split the blocks to which they point;
+  // we cannot safely split blocks while we iterate through the module.
+  std::vector<opt::BasicBlock*> blocks;
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      blocks.push_back(&block);
+    }
+  }
+
+  // Now go through all the block pointers that were gathered.
+  for (auto& block : blocks) {
+    // Probabilistically decide whether to try to split this block.
+    if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+        GetFuzzerContext()->GetChanceOfSplittingBlock()) {
+      continue;
+    }
+    // We are going to try to split this block.  We now need to choose where
+    // to split it.  We do this by finding a base instruction that has a
+    // result id, and an offset from that base instruction.  We would like
+    // offsets to be as small as possible and ideally 0 - we only need offsets
+    // because not all instructions can be identified by a result id (e.g.
+    // OpStore instructions cannot).
+    std::vector<std::pair<uint32_t, uint32_t>> base_offset_pairs;
+    // The initial base instruction is the block label.
+    uint32_t base = block->id();
+    uint32_t offset = 0;
+    // Consider every instruction in the block.  The label is excluded: it is
+    // only necessary to consider it as a base in case the first instruction
+    // in the block does not have a result id.
+    for (auto& inst : *block) {
+      if (inst.HasResultId()) {
+        // In the case that the instruction has a result id, we use the
+        // instruction as its own base, with zero offset.
+        base = inst.result_id();
+        offset = 0;
+      } else {
+        // The instruction does not have a result id, so we need to identify
+        // it via the latest instruction that did have a result id (base), and
+        // an incremented offset.
+        offset++;
+      }
+      base_offset_pairs.emplace_back(base, offset);
+    }
+    // Having identified all the places we might be able to split the block,
+    // we choose one of them.
+    auto base_offset = base_offset_pairs
+        [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+            static_cast<uint32_t>(base_offset_pairs.size()))];
+    auto transformation =
+        TransformationSplitBlock(base_offset.first, base_offset.second,
+                                 GetFuzzerContext()->GetFreshId());
+    // If the position we have chosen turns out to be a valid place to split
+    // the block, we apply the split. Otherwise the block just doesn't get
+    // split.
+    if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) {
+      transformation.Apply(GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation() = transformation.ToMessage();
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h
new file mode 100644
index 0000000..951022b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_split_blocks.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
+#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for splitting blocks in the module, to create more blocks; this
+// can be very useful for giving other passes a chance to apply.
+class FuzzerPassSplitBlocks : public FuzzerPass {
+ public:
+  FuzzerPassSplitBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+                        FuzzerContext* fuzzer_context,
+                        protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassSplitBlocks() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
new file mode 100644
index 0000000..9a05c74
--- /dev/null
+++ b/source/fuzz/fuzzer_util.cpp
@@ -0,0 +1,176 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace fuzzerutil {
+
+bool IsFreshId(opt::IRContext* context, uint32_t id) {
+  return !context->get_def_use_mgr()->GetDef(id);
+}
+
+void UpdateModuleIdBound(opt::IRContext* context, uint32_t id) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
+  //  case where the maximum id bound is reached.
+  context->module()->SetIdBound(
+      std::max(context->module()->id_bound(), id + 1));
+}
+
+opt::BasicBlock* MaybeFindBlock(opt::IRContext* context,
+                                uint32_t maybe_block_id) {
+  auto inst = context->get_def_use_mgr()->GetDef(maybe_block_id);
+  if (inst == nullptr) {
+    // No instruction defining this id was found.
+    return nullptr;
+  }
+  if (inst->opcode() != SpvOpLabel) {
+    // The instruction defining the id is not a label, so it cannot be a block
+    // id.
+    return nullptr;
+  }
+  return context->cfg()->block(maybe_block_id);
+}
+
+bool PhiIdsOkForNewEdge(
+    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
+    const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
+  if (bb_from->IsSuccessor(bb_to)) {
+    // There is already an edge from |from_block| to |to_block|, so there is
+    // no need to extend OpPhi instructions.  Do not allow phi ids to be
+    // present. This might turn out to be too strict; perhaps it would be OK
+    // just to ignore the ids in this case.
+    return phi_ids.empty();
+  }
+  // The edge would add a previously non-existent edge from |from_block| to
+  // |to_block|, so we go through the given phi ids and check that they exactly
+  // match the OpPhi instructions in |to_block|.
+  uint32_t phi_index = 0;
+  // An explicit loop, rather than applying a lambda to each OpPhi in |bb_to|,
+  // makes sense here because we need to increment |phi_index| for each OpPhi
+  // instruction.
+  for (auto& inst : *bb_to) {
+    if (inst.opcode() != SpvOpPhi) {
+      // The OpPhi instructions all occur at the start of the block; if we find
+      // a non-OpPhi then we have seen them all.
+      break;
+    }
+    if (phi_index == static_cast<uint32_t>(phi_ids.size())) {
+      // Not enough phi ids have been provided to account for the OpPhi
+      // instructions.
+      return false;
+    }
+    // Look for an instruction defining the next phi id.
+    opt::Instruction* phi_extension =
+        context->get_def_use_mgr()->GetDef(phi_ids[phi_index]);
+    if (!phi_extension) {
+      // The id given to extend this OpPhi does not exist.
+      return false;
+    }
+    if (phi_extension->type_id() != inst.type_id()) {
+      // The instruction given to extend this OpPhi either does not have a type
+      // or its type does not match that of the OpPhi.
+      return false;
+    }
+
+    if (context->get_instr_block(phi_extension)) {
+      // The instruction defining the phi id has an associated block (i.e., it
+      // is not a global value).  Check whether its definition dominates the
+      // exit of |from_block|.
+      auto dominator_analysis =
+          context->GetDominatorAnalysis(bb_from->GetParent());
+      if (!dominator_analysis->Dominates(phi_extension,
+                                         bb_from->terminator())) {
+        // The given id is no good as its definition does not dominate the exit
+        // of |from_block|
+        return false;
+      }
+    }
+    phi_index++;
+  }
+  // Return false if not all of the ids for extending OpPhi instructions are
+  // needed. This might turn out to be stricter than necessary; perhaps it would
+  // be OK just to not use the ids in this case.
+  return phi_index == static_cast<uint32_t>(phi_ids.size());
+}
+
+void AddUnreachableEdgeAndUpdateOpPhis(
+    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
+    bool condition_value,
+    const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids) {
+  assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) &&
+         "Precondition on phi_ids is not satisfied");
+  assert(bb_from->terminator()->opcode() == SpvOpBranch &&
+         "Precondition on terminator of bb_from is not satisfied");
+
+  // Get the id of the boolean constant to be used as the condition.
+  opt::analysis::Bool bool_type;
+  opt::analysis::BoolConstant bool_constant(
+      context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(),
+      condition_value);
+  uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant(
+      &bool_constant, context->get_type_mgr()->GetId(&bool_type));
+
+  const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
+  auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
+
+  // Add the dead branch, by turning OpBranch into OpBranchConditional, and
+  // ordering the targets depending on whether the given boolean corresponds to
+  // true or false.
+  bb_from->terminator()->SetOpcode(SpvOpBranchConditional);
+  bb_from->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_ID, {bool_id}},
+       {SPV_OPERAND_TYPE_ID, {condition_value ? successor : bb_to->id()}},
+       {SPV_OPERAND_TYPE_ID, {condition_value ? bb_to->id() : successor}}});
+
+  // Update OpPhi instructions in the target block if this branch adds a
+  // previously non-existent edge from source to target.
+  if (!from_to_edge_already_exists) {
+    uint32_t phi_index = 0;
+    for (auto& inst : *bb_to) {
+      if (inst.opcode() != SpvOpPhi) {
+        break;
+      }
+      assert(phi_index < static_cast<uint32_t>(phi_ids.size()) &&
+             "There should be exactly one phi id per OpPhi instruction.");
+      inst.AddOperand({SPV_OPERAND_TYPE_ID, {phi_ids[phi_index]}});
+      inst.AddOperand({SPV_OPERAND_TYPE_ID, {bb_from->id()}});
+      phi_index++;
+    }
+    assert(phi_index == static_cast<uint32_t>(phi_ids.size()) &&
+           "There should be exactly one phi id per OpPhi instruction.");
+  }
+}
+
+bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
+                                    uint32_t maybe_loop_header_id) {
+  // We deem a block to be part of a loop's continue construct if the loop's
+  // continue target dominates the block.
+  auto containing_construct_block = context->cfg()->block(maybe_loop_header_id);
+  if (containing_construct_block->IsLoopHeader()) {
+    auto continue_target = containing_construct_block->ContinueBlockId();
+    if (context->GetDominatorAnalysis(containing_construct_block->GetParent())
+            ->Dominates(continue_target, block_id)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace fuzzerutil
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
new file mode 100644
index 0000000..15228de
--- /dev/null
+++ b/source/fuzz/fuzzer_util.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_UTIL_H_
+#define SOURCE_FUZZ_FUZZER_UTIL_H_
+
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Provides global utility methods for use by the fuzzer
+namespace fuzzerutil {
+
+// Returns true if and only if the module does not define the given id.
+bool IsFreshId(opt::IRContext* context, uint32_t id);
+
+// Updates the module's id bound if needed so that it is large enough to
+// account for the given id.
+void UpdateModuleIdBound(opt::IRContext* context, uint32_t id);
+
+// Return the block with id |maybe_block_id| if it exists, and nullptr
+// otherwise.
+opt::BasicBlock* MaybeFindBlock(opt::IRContext* context,
+                                uint32_t maybe_block_id);
+
+// When adding an edge from |bb_from| to |bb_to| (which are assumed to be blocks
+// in the same function), it is important to supply |bb_to| with ids that can be
+// used to augment OpPhi instructions in the case that there is not already such
+// an edge.  This function returns true if and only if the ids provided in
+// |phi_ids| suffice for this purpose,
+bool PhiIdsOkForNewEdge(
+    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
+    const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
+
+// Requires that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) holds,
+// and that bb_from ends with "OpBranch %some_block".  Turns OpBranch into
+// "OpBranchConditional |condition_value| ...", such that control will branch
+// to %some_block, with |bb_to| being the unreachable alternative.  Updates
+// OpPhi instructions in |bb_to| using |phi_ids| so that the new edge is valid.
+void AddUnreachableEdgeAndUpdateOpPhis(
+    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
+    bool condition_value,
+    const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
+
+// Returns true if and only if |maybe_loop_header_id| is a loop header and
+// |block_id| is in the continue construct of the associated loop.
+bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
+                                    uint32_t maybe_loop_header_id);
+
+}  // namespace fuzzerutil
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_UTIL_H_
diff --git a/source/fuzz/id_use_descriptor.cpp b/source/fuzz/id_use_descriptor.cpp
new file mode 100644
index 0000000..33d2ca0
--- /dev/null
+++ b/source/fuzz/id_use_descriptor.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/id_use_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+opt::Instruction* transformation::FindInstruction(
+    const protobufs::IdUseDescriptor& descriptor,
+    spvtools::opt::IRContext* context) {
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      bool found_base = block.id() == descriptor.base_instruction_result_id();
+      uint32_t num_ignored = 0;
+      for (auto& instruction : block) {
+        if (instruction.HasResultId() &&
+            instruction.result_id() ==
+                descriptor.base_instruction_result_id()) {
+          assert(!found_base &&
+                 "It should not be possible to find the base instruction "
+                 "multiple times.");
+          found_base = true;
+          assert(num_ignored == 0 &&
+                 "The skipped instruction count should only be incremented "
+                 "after the instruction base has been found.");
+        }
+        if (found_base &&
+            instruction.opcode() == descriptor.target_instruction_opcode()) {
+          if (num_ignored == descriptor.num_opcodes_to_ignore()) {
+            if (descriptor.in_operand_index() >= instruction.NumInOperands()) {
+              return nullptr;
+            }
+            auto in_operand =
+                instruction.GetInOperand(descriptor.in_operand_index());
+            if (in_operand.type != SPV_OPERAND_TYPE_ID) {
+              return nullptr;
+            }
+            if (in_operand.words[0] != descriptor.id_of_interest()) {
+              return nullptr;
+            }
+            return &instruction;
+          }
+          num_ignored++;
+        }
+      }
+      if (found_base) {
+        // We found the base instruction, but did not find the target
+        // instruction in the same block.
+        return nullptr;
+      }
+    }
+  }
+  return nullptr;
+}
+
+protobufs::IdUseDescriptor transformation::MakeIdUseDescriptor(
+    uint32_t id_of_interest, SpvOp target_instruction_opcode,
+    uint32_t in_operand_index, uint32_t base_instruction_result_id,
+    uint32_t num_opcodes_to_ignore) {
+  protobufs::IdUseDescriptor result;
+  result.set_id_of_interest(id_of_interest);
+  result.set_target_instruction_opcode(target_instruction_opcode);
+  result.set_in_operand_index(in_operand_index);
+  result.set_base_instruction_result_id(base_instruction_result_id);
+  result.set_num_opcodes_to_ignore(num_opcodes_to_ignore);
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/id_use_descriptor.h b/source/fuzz/id_use_descriptor.h
new file mode 100644
index 0000000..63016c5
--- /dev/null
+++ b/source/fuzz/id_use_descriptor.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_ID_USE_LOCATOR_H_
+#define SOURCE_FUZZ_ID_USE_LOCATOR_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+// Looks for an instruction in |context| such that the id use represented by
+// |descriptor| is one of the operands to said instruction.  Returns |nullptr|
+// if no such instruction can be found.
+opt::Instruction* FindInstruction(const protobufs::IdUseDescriptor& descriptor,
+                                  opt::IRContext* context);
+
+// Creates an IdUseDescriptor protobuf message from the given components.
+// See the protobuf definition for details of what these components mean.
+protobufs::IdUseDescriptor MakeIdUseDescriptor(
+    uint32_t id_of_interest, SpvOp target_instruction_opcode,
+    uint32_t in_operand_index, uint32_t base_instruction_result_id,
+    uint32_t num_opcodes_to_ignore);
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_ID_USE_LOCATOR_H_
diff --git a/source/fuzz/protobufs/spirvfuzz_protobufs.h b/source/fuzz/protobufs/spirvfuzz_protobufs.h
new file mode 100644
index 0000000..b801626
--- /dev/null
+++ b/source/fuzz/protobufs/spirvfuzz_protobufs.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_SPIRVFUZZ_PROTOBUFS_H_
+#define SOURCE_FUZZ_SPIRVFUZZ_PROTOBUFS_H_
+
+// This header file serves to act as a barrier between the protobuf header
+// files and files that include them.  It uses compiler pragmas to disable
+// diagnostics, in order to ignore warnings generated during the processing
+// of these header files without having to compromise on freedom from warnings
+// in the rest of the project.
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wshadow"
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#elif defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable : 4244)
+#endif
+
+// The following should be the only place in the project where protobuf files
+// are directly included.  This is so that they can be compiled in a manner
+// where warnings are ignored.
+
+#include "google/protobuf/util/json_util.h"
+#include "source/fuzz/protobufs/spvtoolsfuzz.pb.h"
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#endif  // SOURCE_FUZZ_SPIRVFUZZ_PROTOBUFS_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
new file mode 100644
index 0000000..13d8a05
--- /dev/null
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -0,0 +1,335 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file is specifically named spvtools_fuzz.proto so that the string
+// 'spvtools_fuzz' appears in the names of global-scope symbols that protoc
+// generates when targeting C++.  This is to reduce the potential for name
+// clashes with other globally-scoped symbols.
+
+syntax = "proto3";
+
+package spvtools.fuzz.protobufs;
+
+message IdUseDescriptor {
+
+  // Describes a use of an id as an input operand to an instruction in some block
+  // of a function.
+
+  // Example:
+  //   - id_of_interest = 42
+  //   - target_instruction_opcode = OpStore
+  //   - in_operand_index = 1
+  //   - base_instruction_result_id = 50
+  //   - num_opcodes_to_ignore = 7
+  // represents a use of id 42 as input operand 1 to an OpStore instruction,
+  // such that the OpStore instruction can be found in the same basic block as
+  // the instruction with result id 50, and in particular is the 8th OpStore
+  // instruction found from instruction 50 onwards (i.e. 7 OpStore
+  // instructions are skipped).
+
+  // An id that we would like to be able to find a use of.
+  uint32 id_of_interest = 1;
+
+  // The opcode for the instruction that uses the id.
+  uint32 target_instruction_opcode = 2;
+
+  // The input operand index at which the use is expected.
+  uint32 in_operand_index = 3;
+
+  // The id of an instruction after which the instruction that contains the use
+  // is believed to occur; it might be the using instruction itself.
+  uint32 base_instruction_result_id = 4;
+
+  // The number of matching opcodes to skip over when searching for the using
+  // instruction from the base instruction.
+  uint32 num_opcodes_to_ignore = 5;
+
+}
+
+message UniformBufferElementDescriptor {
+
+  // Represents a data element inside a uniform buffer.  The element is
+  // specified via (a) the result id of a uniform variable in which the element
+  // is contained, and (b) a series of indices that need to be followed to get
+  // to the element (via fields and array/vector indices).
+  //
+  // Example: suppose there is a uniform variable with descriptor set 7 and
+  // binding 9, and that the uniform variable has the following type (using
+  // GLSL-like syntax):
+  //
+  // struct S {
+  //   float f;
+  //   vec3 g;
+  //   int4 h[10];
+  // };
+  //
+  // Then:
+  // - (7, 9, [0]) describes the 'f' field.
+  // - (7, 9, [1,1]) describes the y component of the 'g' field.
+  // - (7, 9, [2,7,3]) describes the w component of element 7 of the 'h' field
+
+  // The descriptor set and binding associated with a uniform variable.
+  uint32 descriptor_set = 1;
+  uint32 binding = 2;
+
+  // An ordered sequence of indices through composite structures in the
+  // uniform buffer.
+  repeated uint32 index = 3;
+
+}
+
+message FactSequence {
+  repeated Fact fact = 1;
+}
+
+message Fact {
+  oneof fact {
+    // Order the fact options by numeric id (rather than alphabetically).
+    FactConstantUniform constant_uniform_fact = 1;
+  }
+}
+
+// Keep fact message types in alphabetical order:
+
+message FactConstantUniform {
+
+  // Records the fact that a uniform buffer element is guaranteed to be equal
+  // to a particular constant value.  spirv-fuzz can use such guarantees to
+  // obfuscate code, e.g. to manufacture an expression that will (due to the
+  // guarantee) evaluate to a particular value at runtime but in a manner that
+  // cannot be predicted at compile-time.
+
+  // An element of a uniform buffer
+  UniformBufferElementDescriptor uniform_buffer_element_descriptor = 1;
+
+  // The words of the associated constant
+  repeated uint32 constant_word = 2;
+
+}
+
+message TransformationSequence {
+  repeated Transformation transformation = 1;
+}
+
+message Transformation {
+  oneof transformation {
+    // Order the transformation options by numeric id (rather than
+    // alphabetically).
+    TransformationMoveBlockDown move_block_down = 1;
+    TransformationSplitBlock split_block = 2;
+    TransformationAddConstantBoolean add_constant_boolean = 3;
+    TransformationAddConstantScalar add_constant_scalar = 4;
+    TransformationAddTypeBoolean add_type_boolean = 5;
+    TransformationAddTypeFloat add_type_float = 6;
+    TransformationAddTypeInt add_type_int = 7;
+    TransformationAddDeadBreak add_dead_break = 8;
+    TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
+    TransformationAddTypePointer add_type_pointer = 10;
+    TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
+    TransformationAddDeadContinue add_dead_continue = 12;
+    // Add additional option using the next available number.
+  }
+}
+
+// Keep transformation message types in alphabetical order:
+
+message TransformationAddConstantBoolean {
+
+  // Supports adding the constants true and false to a module, which may be
+  // necessary in order to enable other transformations if they are not present.
+
+  uint32 fresh_id = 1;
+  bool is_true = 2;
+
+}
+
+message TransformationAddConstantScalar {
+
+  // Adds a constant of the given scalar type
+
+  // Id for the constant
+  uint32 fresh_id = 1;
+
+  // Id for the scalar type of the constant
+  uint32 type_id = 2;
+
+  // Value of the constant
+  repeated uint32 word = 3;
+
+}
+
+message TransformationAddDeadBreak {
+
+  // A transformation that turns a basic block that unconditionally branches to
+  // its successor into a block that potentially breaks out of a structured
+  // control flow construct, but in such a manner that the break cannot actually
+  // be taken.
+
+  // The block to break from
+  uint32 from_block = 1;
+
+  // The merge block to break to
+  uint32 to_block = 2;
+
+  // Determines whether the break condition is true or false
+  bool break_condition_value = 3;
+
+  // A sequence of ids suitable for extending OpPhi instructions as a result of
+  // the new break edge
+  repeated uint32 phi_id = 4;
+
+}
+
+message TransformationAddDeadContinue {
+
+  // A transformation that turns a basic block appearing in a loop and that
+  // unconditionally branches to its successor into a block that potentially
+  // branches to the continue target of the loop, but in such a manner that the
+  // continue branch cannot actually be taken.
+
+  // The block to continue from
+  uint32 from_block = 1;
+
+  // Determines whether the continue condition is true or false
+  bool continue_condition_value = 2;
+
+  // A sequence of ids suitable for extending OpPhi instructions as a result of
+  // the new break edge
+  repeated uint32 phi_id = 3;
+
+}
+
+message TransformationAddTypeBoolean {
+
+  // Adds OpTypeBool to the module
+
+  // Id to be used for the type
+  uint32 fresh_id = 1;
+
+}
+
+message TransformationAddTypeFloat {
+
+  // Adds OpTypeFloat to the module with the given width
+
+  // Id to be used for the type
+  uint32 fresh_id = 1;
+
+  // Floating-point width
+  uint32 width = 2;
+
+}
+
+message TransformationAddTypeInt {
+
+  // Adds OpTypeInt to the module with the given width and signedness
+
+  // Id to be used for the type
+  uint32 fresh_id = 1;
+
+  // Integer width
+  uint32 width = 2;
+
+  // True if and only if this is a signed type
+  bool is_signed = 3;
+
+}
+
+message TransformationAddTypePointer {
+
+  // Adds OpTypePointer to the module, with the given storage class and base
+  // type
+
+  // Id to be used for the type
+  uint32 fresh_id = 1;
+
+  // Pointer storage class
+  uint32 storage_class = 2;
+
+  // Id of the base type for the pointer
+  uint32 base_type_id = 3;
+
+}
+
+message TransformationMoveBlockDown {
+
+  // A transformation that moves a basic block to be one position lower in
+  // program order.
+
+  // The id of the block to move down.
+  uint32 block_id = 1;
+}
+
+message TransformationReplaceConstantWithUniform {
+
+  // Replaces a use of a constant id with the the result of a load from an
+  // element of uniform buffer known to hold the same value as the constant
+
+  // A descriptor for the id we would like to replace
+  IdUseDescriptor id_use_descriptor = 1;
+
+  // Uniform descriptor to identify which uniform value to choose
+  UniformBufferElementDescriptor uniform_descriptor = 2;
+
+  // Id that will store the result of an access chain
+  uint32 fresh_id_for_access_chain = 3;
+
+  // Id that will store the result of a load
+  uint32 fresh_id_for_load = 4;
+
+}
+
+message TransformationReplaceBooleanConstantWithConstantBinary {
+  // A transformation to capture replacing a use of a boolean constant with
+  // binary operation on two constant values
+
+  // 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
+  uint32 lhs_id = 2;
+
+  // Id for the constant to be used on the RHS of the comparision
+  uint32 rhs_id = 3;
+
+  // Opcode for binary operator
+  uint32 opcode = 4;
+
+  // Id that will store the result of the binary operation instruction
+  uint32 fresh_id_for_binary_operation = 5;
+
+}
+
+message TransformationSplitBlock {
+
+  // A transformation that splits a basic block into two basic blocks.
+
+  // The result id of an instruction.
+  uint32 result_id = 1;
+
+  // An offset, such that the block containing |result_id_| should be split
+  // right before the instruction |offset_| instructions after |result_id_|.
+  uint32 offset = 2;
+
+  // An id that must not yet be used by the module to which this transformation
+  // is applied.  Rather than having the transformation choose a suitable id on
+  // application, we require the id to be given upfront in order to facilitate
+  // reducing fuzzed shaders by removing transformations.  The reason is that
+  // future transformations may refer to the fresh id introduced by this
+  // transformation, and if we end up changing what that id is, due to removing
+  // earlier transformations, it may inhibit later transformations from
+  // applying.
+  uint32 fresh_id = 3;
+
+}
diff --git a/source/fuzz/pseudo_random_generator.cpp b/source/fuzz/pseudo_random_generator.cpp
new file mode 100644
index 0000000..9643264
--- /dev/null
+++ b/source/fuzz/pseudo_random_generator.cpp
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/pseudo_random_generator.h"
+
+#include <cassert>
+
+namespace spvtools {
+namespace fuzz {
+
+PseudoRandomGenerator::PseudoRandomGenerator(uint32_t seed) : mt_(seed) {}
+
+PseudoRandomGenerator::~PseudoRandomGenerator() = default;
+
+uint32_t PseudoRandomGenerator::RandomUint32(uint32_t bound) {
+  assert(bound > 0 && "Bound must be positive");
+  return static_cast<uint32_t>(
+      std::uniform_int_distribution<>(0, bound - 1)(mt_));
+}
+
+bool PseudoRandomGenerator::RandomBool() {
+  return static_cast<bool>(std::uniform_int_distribution<>(0, 1)(mt_));
+}
+
+uint32_t PseudoRandomGenerator::RandomPercentage() {
+  // We use 101 because we want a result in the closed interval [0, 100], and
+  // RandomUint32 is not inclusive of its bound.
+  return RandomUint32(101);
+}
+
+double PseudoRandomGenerator::RandomDouble() {
+  return std::uniform_real_distribution<double>(0.0, 1.0)(mt_);
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pseudo_random_generator.h b/source/fuzz/pseudo_random_generator.h
new file mode 100644
index 0000000..d2f5292
--- /dev/null
+++ b/source/fuzz/pseudo_random_generator.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_PSEUDO_RANDOM_GENERATOR_H_
+#define SOURCE_FUZZ_PSEUDO_RANDOM_GENERATOR_H_
+
+#include <random>
+
+#include "source/fuzz/random_generator.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Generates random data from a pseudo-random number generator.
+class PseudoRandomGenerator : public RandomGenerator {
+ public:
+  explicit PseudoRandomGenerator(uint32_t seed);
+
+  ~PseudoRandomGenerator() override;
+
+  uint32_t RandomUint32(uint32_t bound) override;
+
+  uint32_t RandomPercentage() override;
+
+  bool RandomBool() override;
+
+  double RandomDouble() override;
+
+ private:
+  std::mt19937 mt_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_PSEUDO_RANDOM_GENERATOR_H_
diff --git a/source/fuzz/random_generator.cpp b/source/fuzz/random_generator.cpp
new file mode 100644
index 0000000..9ec4845
--- /dev/null
+++ b/source/fuzz/random_generator.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/random_generator.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RandomGenerator::RandomGenerator() = default;
+
+RandomGenerator::~RandomGenerator() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/random_generator.h b/source/fuzz/random_generator.h
new file mode 100644
index 0000000..9c46798
--- /dev/null
+++ b/source/fuzz/random_generator.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_RANDOM_GENERATOR_H_
+#define SOURCE_FUZZ_RANDOM_GENERATOR_H_
+
+#include <stdint.h>
+
+namespace spvtools {
+namespace fuzz {
+
+class RandomGenerator {
+ public:
+  RandomGenerator();
+
+  virtual ~RandomGenerator();
+
+  // Returns a value in the half-open interval [0, bound).
+  virtual uint32_t RandomUint32(uint32_t bound) = 0;
+
+  // Returns a value in the closed interval [0, 100].
+  virtual uint32_t RandomPercentage() = 0;
+
+  // Returns a boolean.
+  virtual bool RandomBool() = 0;
+
+  // Returns a double in the closed interval [0, 1]
+  virtual double RandomDouble() = 0;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_RANDOM_GENERATOR_H_
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
new file mode 100644
index 0000000..b0d4ee2
--- /dev/null
+++ b/source/fuzz/replayer.cpp
@@ -0,0 +1,105 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/replayer.h"
+
+#include <utility>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_add_constant_boolean.h"
+#include "source/fuzz/transformation_add_constant_scalar.h"
+#include "source/fuzz/transformation_add_dead_break.h"
+#include "source/fuzz/transformation_add_type_boolean.h"
+#include "source/fuzz/transformation_add_type_float.h"
+#include "source/fuzz/transformation_add_type_int.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+#include "source/fuzz/transformation_move_block_down.h"
+#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/fuzz/transformation_split_block.h"
+#include "source/opt/build_module.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+struct Replayer::Impl {
+  explicit Impl(spv_target_env env) : target_env(env) {}
+
+  const spv_target_env target_env;  // Target environment.
+  MessageConsumer consumer;         // Message consumer.
+};
+
+Replayer::Replayer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
+
+Replayer::~Replayer() = default;
+
+void Replayer::SetMessageConsumer(MessageConsumer c) {
+  impl_->consumer = std::move(c);
+}
+
+Replayer::ReplayerResultStatus Replayer::Run(
+    const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    const protobufs::TransformationSequence& transformation_sequence_in,
+    std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out) const {
+  // Check compatibility between the library version being linked with and the
+  // header files being used.
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  if (!tools.IsValid()) {
+    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
+                    "Failed to create SPIRV-Tools interface; stopping.");
+    return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface;
+  }
+
+  // Initial binary should be valid.
+  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
+  }
+
+  // Build the module from the input binary.
+  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
+      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+  assert(ir_context);
+
+  FactManager fact_manager;
+  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+
+  // Consider the transformation proto messages in turn.
+  for (auto& message : transformation_sequence_in.transformation()) {
+    auto transformation = Transformation::FromMessage(message);
+
+    // Check whether the transformation can be applied.
+    if (transformation->IsApplicable(ir_context.get(), fact_manager)) {
+      // The transformation is applicable, so apply it, and copy it to the
+      // sequence of transformations that were applied.
+      transformation->Apply(ir_context.get(), &fact_manager);
+      *transformation_sequence_out->add_transformation() = message;
+    }
+  }
+
+  // Write out the module as a binary.
+  ir_context->module()->ToBinary(binary_out, false);
+  return Replayer::ReplayerResultStatus::kComplete;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
new file mode 100644
index 0000000..13391d0
--- /dev/null
+++ b/source/fuzz/replayer.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_REPLAYER_H_
+#define SOURCE_FUZZ_REPLAYER_H_
+
+#include <memory>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// Transforms a SPIR-V module into a semantically equivalent SPIR-V module by
+// applying a series of pre-defined transformations.
+class Replayer {
+ public:
+  // Possible statuses that can result from running the replayer.
+  enum ReplayerResultStatus {
+    kComplete,
+    kFailedToCreateSpirvToolsInterface,
+    kInitialBinaryInvalid,
+  };
+
+  // Constructs a replayer from the given target environment.
+  explicit Replayer(spv_target_env env);
+
+  // Disables copy/move constructor/assignment operations.
+  Replayer(const Replayer&) = delete;
+  Replayer(Replayer&&) = delete;
+  Replayer& operator=(const Replayer&) = delete;
+  Replayer& operator=(Replayer&&) = delete;
+
+  ~Replayer();
+
+  // Sets the message consumer to the given |consumer|. The |consumer| will be
+  // invoked once for each message communicated from the library.
+  void SetMessageConsumer(MessageConsumer consumer);
+
+  // Transforms |binary_in| to |binary_out| by attempting to apply the
+  // transformations from |transformation_sequence_in|.  Initial facts about the
+  // input binary and the context in which it will execute are provided via
+  // |initial_facts|.  The transformations that were successfully applied are
+  // returned via |transformation_sequence_out|.
+  ReplayerResultStatus Run(
+      const std::vector<uint32_t>& binary_in,
+      const protobufs::FactSequence& initial_facts,
+      const protobufs::TransformationSequence& transformation_sequence_in,
+      std::vector<uint32_t>* binary_out,
+      protobufs::TransformationSequence* transformation_sequence_out) const;
+
+ private:
+  struct Impl;                  // Opaque struct for holding internal data.
+  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPLAYER_H_
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
new file mode 100644
index 0000000..f8d8aa3
--- /dev/null
+++ b/source/fuzz/shrinker.cpp
@@ -0,0 +1,240 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/shrinker.h"
+
+#include <sstream>
+
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/replayer.h"
+#include "source/spirv_fuzzer_options.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+
+// A helper to get the size of a protobuf transformation sequence in a less
+// verbose manner.
+uint32_t NumRemainingTransformations(
+    const protobufs::TransformationSequence& transformation_sequence) {
+  return static_cast<uint32_t>(transformation_sequence.transformation_size());
+}
+
+// A helper to return a transformation sequence identical to |transformations|,
+// except that a chunk of size |chunk_size| starting from |chunk_index| x
+// |chunk_size| is removed (or as many transformations as available if the whole
+// chunk is not).
+protobufs::TransformationSequence RemoveChunk(
+    const protobufs::TransformationSequence& transformations,
+    uint32_t chunk_index, uint32_t chunk_size) {
+  uint32_t lower = chunk_index * chunk_size;
+  uint32_t upper = std::min((chunk_index + 1) * chunk_size,
+                            NumRemainingTransformations(transformations));
+  assert(lower < upper);
+  assert(upper <= NumRemainingTransformations(transformations));
+  protobufs::TransformationSequence result;
+  for (uint32_t j = 0; j < NumRemainingTransformations(transformations); j++) {
+    if (j >= lower && j < upper) {
+      continue;
+    }
+    protobufs::Transformation transformation =
+        transformations.transformation()[j];
+    *result.mutable_transformation()->Add() = transformation;
+  }
+  return result;
+}
+
+}  // namespace
+
+struct Shrinker::Impl {
+  explicit Impl(spv_target_env env, uint32_t limit)
+      : target_env(env), step_limit(limit) {}
+
+  const spv_target_env target_env;  // Target environment.
+  MessageConsumer consumer;         // Message consumer.
+  const uint32_t step_limit;        // Step limit for reductions.
+};
+
+Shrinker::Shrinker(spv_target_env env, uint32_t step_limit)
+    : impl_(MakeUnique<Impl>(env, step_limit)) {}
+
+Shrinker::~Shrinker() = default;
+
+void Shrinker::SetMessageConsumer(MessageConsumer c) {
+  impl_->consumer = std::move(c);
+}
+
+Shrinker::ShrinkerResultStatus Shrinker::Run(
+    const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    const protobufs::TransformationSequence& transformation_sequence_in,
+    const Shrinker::InterestingnessFunction& interestingness_function,
+    std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out) const {
+  // Check compatibility between the library version being linked with and the
+  // header files being used.
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  if (!tools.IsValid()) {
+    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
+                    "Failed to create SPIRV-Tools interface; stopping.");
+    return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface;
+  }
+
+  // Initial binary should be valid.
+  if (!tools.Validate(&binary_in[0], binary_in.size())) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
+  }
+
+  std::vector<uint32_t> current_best_binary;
+  protobufs::TransformationSequence current_best_transformations;
+
+  // Run a replay of the initial transformation sequence to (a) check that it
+  // succeeds, (b) get the binary that results from running these
+  // transformations, and (c) get the subsequence of the initial transformations
+  // that actually apply (in principle this could be a strict subsequence).
+  if (Replayer(impl_->target_env)
+          .Run(binary_in, initial_facts, transformation_sequence_in,
+               &current_best_binary, &current_best_transformations) !=
+      Replayer::ReplayerResultStatus::kComplete) {
+    return ShrinkerResultStatus::kReplayFailed;
+  }
+
+  // Check that the binary produced by applying the initial transformations is
+  // indeed interesting.
+  if (!interestingness_function(current_best_binary, 0)) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is not interesting; stopping.");
+    return ShrinkerResultStatus::kInitialBinaryNotInteresting;
+  }
+
+  uint32_t attempt = 0;  // Keeps track of the number of shrink attempts that
+                         // have been tried, whether successful or not.
+
+  uint32_t chunk_size =
+      std::max(1u, NumRemainingTransformations(current_best_transformations) /
+                       2);  // The number of contiguous transformations that the
+                            // shrinker will try to remove in one go; starts
+                            // high and decreases during the shrinking process.
+
+  // Keep shrinking until we:
+  // - reach the step limit,
+  // - run out of transformations to remove, or
+  // - cannot make the chunk size any smaller.
+  while (attempt < impl_->step_limit &&
+         !current_best_transformations.transformation().empty() &&
+         chunk_size > 0) {
+    bool progress_this_round =
+        false;  // Used to decide whether to make the chunk size with which we
+                // remove transformations smaller.  If we managed to remove at
+                // least one chunk of transformations at a particular chunk
+                // size, we set this flag so that we do not yet decrease the
+                // chunk size.
+
+    assert(chunk_size <=
+               NumRemainingTransformations(current_best_transformations) &&
+           "Chunk size should never exceed the number of transformations that "
+           "remain.");
+
+    // The number of chunks is the ceiling of (#remaining_transformations /
+    // chunk_size).
+    const uint32_t num_chunks =
+        (NumRemainingTransformations(current_best_transformations) +
+         chunk_size - 1) /
+        chunk_size;
+    assert(num_chunks >= 1 && "There should be at least one chunk.");
+    assert(num_chunks * chunk_size >=
+               NumRemainingTransformations(current_best_transformations) &&
+           "All transformations should be in some chunk.");
+
+    // We go through the transformations in reverse, in chunks of size
+    // |chunk_size|, using |chunk_index| to track which chunk to try removing
+    // next.  The loop exits early if we reach the shrinking step limit.
+    for (int chunk_index = num_chunks - 1;
+         attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) {
+      // Remove a chunk of transformations according to the current index and
+      // chunk size.
+      auto transformations_with_chunk_removed =
+          RemoveChunk(current_best_transformations, chunk_index, chunk_size);
+
+      // Replay the smaller sequence of transformations to get a next binary and
+      // transformation sequence. Note that the transformations arising from
+      // replay might be even smaller than the transformations with the chunk
+      // removed, because removing those transformations might make further
+      // transformations inapplicable.
+      std::vector<uint32_t> next_binary;
+      protobufs::TransformationSequence next_transformation_sequence;
+      if (Replayer(impl_->target_env)
+              .Run(binary_in, initial_facts, transformations_with_chunk_removed,
+                   &next_binary, &next_transformation_sequence) !=
+          Replayer::ReplayerResultStatus::kComplete) {
+        // Replay should not fail; if it does, we need to abort shrinking.
+        return ShrinkerResultStatus::kReplayFailed;
+      }
+
+      assert(NumRemainingTransformations(next_transformation_sequence) >=
+                 chunk_index * chunk_size &&
+             "Removing this chunk of transformations should not have an effect "
+             "on earlier chunks.");
+
+      if (interestingness_function(next_binary, attempt)) {
+        // If the binary arising from the smaller transformation sequence is
+        // interesting, this becomes our current best binary and transformation
+        // sequence.
+        current_best_binary = next_binary;
+        current_best_transformations = next_transformation_sequence;
+        progress_this_round = true;
+      }
+      // Either way, this was a shrink attempt, so increment our count of shrink
+      // attempts.
+      attempt++;
+    }
+    if (!progress_this_round) {
+      // If we didn't manage to remove any chunks at this chunk size, try a
+      // smaller chunk size.
+      chunk_size /= 2;
+    }
+    // Decrease the chunk size until it becomes no larger than the number of
+    // remaining transformations.
+    while (chunk_size >
+           NumRemainingTransformations(current_best_transformations)) {
+      chunk_size /= 2;
+    }
+  }
+
+  // The output from the shrinker is the best binary we saw, and the
+  // transformations that led to it.
+  *binary_out = current_best_binary;
+  *transformation_sequence_out = current_best_transformations;
+
+  // Indicate whether shrinking completed or was truncated due to reaching the
+  // step limit.
+  assert(attempt <= impl_->step_limit);
+  if (attempt == impl_->step_limit) {
+    std::stringstream strstream;
+    strstream << "Shrinking did not complete; step limit " << impl_->step_limit
+              << " was reached.";
+    impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
+    return Shrinker::ShrinkerResultStatus::kStepLimitReached;
+  }
+  return Shrinker::ShrinkerResultStatus::kComplete;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
new file mode 100644
index 0000000..72dd470
--- /dev/null
+++ b/source/fuzz/shrinker.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_SHRINKER_H_
+#define SOURCE_FUZZ_SHRINKER_H_
+
+#include <memory>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// Shrinks a sequence of transformations that lead to an interesting SPIR-V
+// binary to yield a smaller sequence of transformations that still produce an
+// interesting binary.
+class Shrinker {
+ public:
+  // Possible statuses that can result from running the shrinker.
+  enum ShrinkerResultStatus {
+    kComplete,
+    kFailedToCreateSpirvToolsInterface,
+    kInitialBinaryInvalid,
+    kInitialBinaryNotInteresting,
+    kReplayFailed,
+    kStepLimitReached,
+  };
+
+  // The type for a function that will take a binary, |binary|, and return true
+  // if and only if the binary is deemed interesting. (The function also takes
+  // an integer argument, |counter|, that will be incremented each time the
+  // function is called; this is for debugging purposes).
+  //
+  // The notion of "interesting" depends on what properties of the binary or
+  // tools that process the binary we are trying to maintain during shrinking.
+  using InterestingnessFunction = std::function<bool(
+      const std::vector<uint32_t>& binary, uint32_t counter)>;
+
+  // Constructs a shrinker from the given target environment.
+  Shrinker(spv_target_env env, uint32_t step_limit);
+
+  // Disables copy/move constructor/assignment operations.
+  Shrinker(const Shrinker&) = delete;
+  Shrinker(Shrinker&&) = delete;
+  Shrinker& operator=(const Shrinker&) = delete;
+  Shrinker& operator=(Shrinker&&) = delete;
+
+  ~Shrinker();
+
+  // Sets the message consumer to the given |consumer|. The |consumer| will be
+  // invoked once for each message communicated from the library.
+  void SetMessageConsumer(MessageConsumer consumer);
+
+  // Requires that when |transformation_sequence_in| is applied to |binary_in|
+  // with initial facts |initial_facts|, the resulting binary is interesting
+  // according to |interestingness_function|.
+  //
+  // Produces, via |transformation_sequence_out|, a subsequence of
+  // |transformation_sequence_in| that, when applied with initial facts
+  // |initial_facts|, produces a binary (captured via |binary_out|) that is
+  // also interesting according to |interestingness_function|.
+  ShrinkerResultStatus Run(
+      const std::vector<uint32_t>& binary_in,
+      const protobufs::FactSequence& initial_facts,
+      const protobufs::TransformationSequence& transformation_sequence_in,
+      const InterestingnessFunction& interestingness_function,
+      std::vector<uint32_t>* binary_out,
+      protobufs::TransformationSequence* transformation_sequence_out) const;
+
+ private:
+  struct Impl;                  // Opaque struct for holding internal data.
+  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_SHRINKER_H_
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
new file mode 100644
index 0000000..a252734
--- /dev/null
+++ b/source/fuzz/transformation.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation.h"
+
+#include <cassert>
+
+#include "source/util/make_unique.h"
+#include "transformation_add_constant_boolean.h"
+#include "transformation_add_constant_scalar.h"
+#include "transformation_add_dead_break.h"
+#include "transformation_add_dead_continue.h"
+#include "transformation_add_type_boolean.h"
+#include "transformation_add_type_float.h"
+#include "transformation_add_type_int.h"
+#include "transformation_add_type_pointer.h"
+#include "transformation_move_block_down.h"
+#include "transformation_replace_boolean_constant_with_constant_binary.h"
+#include "transformation_replace_constant_with_uniform.h"
+#include "transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+Transformation::~Transformation() = default;
+
+std::unique_ptr<Transformation> Transformation::FromMessage(
+    const protobufs::Transformation& message) {
+  switch (message.transformation_case()) {
+    case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
+      return MakeUnique<TransformationAddConstantBoolean>(
+          message.add_constant_boolean());
+    case protobufs::Transformation::TransformationCase::kAddConstantScalar:
+      return MakeUnique<TransformationAddConstantScalar>(
+          message.add_constant_scalar());
+    case protobufs::Transformation::TransformationCase::kAddDeadBreak:
+      return MakeUnique<TransformationAddDeadBreak>(message.add_dead_break());
+    case protobufs::Transformation::TransformationCase::kAddDeadContinue:
+      return MakeUnique<TransformationAddDeadContinue>(
+          message.add_dead_continue());
+    case protobufs::Transformation::TransformationCase::kAddTypeBoolean:
+      return MakeUnique<TransformationAddTypeBoolean>(
+          message.add_type_boolean());
+    case protobufs::Transformation::TransformationCase::kAddTypeFloat:
+      return MakeUnique<TransformationAddTypeFloat>(message.add_type_float());
+    case protobufs::Transformation::TransformationCase::kAddTypeInt:
+      return MakeUnique<TransformationAddTypeInt>(message.add_type_int());
+    case protobufs::Transformation::TransformationCase::kAddTypePointer:
+      return MakeUnique<TransformationAddTypePointer>(
+          message.add_type_pointer());
+    case protobufs::Transformation::TransformationCase::kMoveBlockDown:
+      return MakeUnique<TransformationMoveBlockDown>(message.move_block_down());
+    case protobufs::Transformation::TransformationCase::
+        kReplaceBooleanConstantWithConstantBinary:
+      return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
+          message.replace_boolean_constant_with_constant_binary());
+    case protobufs::Transformation::TransformationCase::
+        kReplaceConstantWithUniform:
+      return MakeUnique<TransformationReplaceConstantWithUniform>(
+          message.replace_constant_with_uniform());
+    case protobufs::Transformation::TransformationCase::kSplitBlock:
+      return MakeUnique<TransformationSplitBlock>(message.split_block());
+    default:
+      assert(message.transformation_case() ==
+                 protobufs::Transformation::TRANSFORMATION_NOT_SET &&
+             "Unhandled transformation type.");
+      assert(false && "An unset transformation was encountered.");
+      return nullptr;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation.h b/source/fuzz/transformation.h
new file mode 100644
index 0000000..c6b852f
--- /dev/null
+++ b/source/fuzz/transformation.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_H_
+
+#include <memory>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Rules for transformations
+// -------------------------
+//
+// - Immutability: a transformation must be immutable.
+// - Ability to copy and serialize: to ensure that a copy of a transformation,
+//     possibly saved out to disk and read back again, is indistinguishable
+//     from the original transformation, thus a transformation must depend
+//     only on well-defined pieces of state, such as instruction ids.  It must
+//     not rely on state such as pointers to instructions and blocks.
+// - Determinism: the effect of a transformation on a module be a deterministic
+//     function of the module and the transformation.  Any randomization should
+//     be applied before creating the transformation, not during its
+//     application.
+// - Well-defined and precondition: the 'IsApplicable' method should only
+//     return true if the transformation can be cleanly applied to the given
+//     module, to mutate it into a valid and semantically-equivalent module, as
+//     long as the module is initially valid.
+// - Ability to test precondition on any valid module: 'IsApplicable' should be
+//     designed so that it is safe to ask whether a transformation is
+//     applicable to an arbitrary valid module.  For example, if a
+//     transformation involves a block id, 'IsApplicable' should check whether
+//     the module indeed has a block with that id, and return false if not.  It
+//     must not assume that there is such a block.
+// - Documented precondition: while the implementation of 'IsApplicable' should
+//     should codify the precondition, the method should be commented in the
+//     header file for a transformation with a precise English description of
+//     the precondition.
+// - Documented effect: while the implementation of 'Apply' should codify the
+//     effect of the transformation, the method should be commented in the
+//     header file for a transformation with a precise English description of
+//     the effect.
+
+class Transformation {
+ public:
+  // A precondition that determines whether the transformation can be cleanly
+  // applied in a semantics-preserving manner to the SPIR-V module given by
+  // |context|, in the presence of facts captured by |fact_manager|.
+  // Preconditions for individual transformations must be documented in the
+  // associated header file using precise English. The fact manager is used to
+  // provide access to facts about the module that are known to be true, on
+  // which the precondition may depend.
+  virtual bool IsApplicable(opt::IRContext* context,
+                            const FactManager& fact_manager) const = 0;
+
+  // Requires that IsApplicable(context, fact_manager) holds.  Applies the
+  // transformation, mutating |context| and possibly updating |fact_manager|
+  // with new facts established by the transformation.
+  virtual void Apply(opt::IRContext* context,
+                     FactManager* fact_manager) const = 0;
+
+  // Turns the transformation into a protobuf message for serialization.
+  virtual protobufs::Transformation ToMessage() const = 0;
+
+  virtual ~Transformation();
+
+  // Factory method to obtain a transformation object from the protobuf
+  // representation of a transformation given by |message|.
+  static std::unique_ptr<Transformation> FromMessage(
+      const protobufs::Transformation& message);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_H_
diff --git a/source/fuzz/transformation_add_constant_boolean.cpp b/source/fuzz/transformation_add_constant_boolean.cpp
new file mode 100644
index 0000000..21c8ed3
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_boolean.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_constant_boolean.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/types.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddConstantBoolean::TransformationAddConstantBoolean(
+    const protobufs::TransformationAddConstantBoolean& message)
+    : message_(message) {}
+
+TransformationAddConstantBoolean::TransformationAddConstantBoolean(
+    uint32_t fresh_id, bool is_true) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_is_true(is_true);
+}
+
+bool TransformationAddConstantBoolean::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  opt::analysis::Bool bool_type;
+  if (!context->get_type_mgr()->GetId(&bool_type)) {
+    // No OpTypeBool is present.
+    return false;
+  }
+  return fuzzerutil::IsFreshId(context, message_.fresh_id());
+}
+
+void TransformationAddConstantBoolean::Apply(opt::IRContext* context,
+                                             FactManager* /*unused*/) const {
+  opt::analysis::Bool bool_type;
+  // Add the boolean constant to the module, ensuring the module's id bound is
+  // high enough.
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  context->module()->AddGlobalValue(
+      message_.is_true() ? SpvOpConstantTrue : SpvOpConstantFalse,
+      message_.fresh_id(), context->get_type_mgr()->GetId(&bool_type));
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddConstantBoolean::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_constant_boolean() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_boolean.h b/source/fuzz/transformation_add_constant_boolean.h
new file mode 100644
index 0000000..79df1cd
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_boolean.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_BOOLEAN_CONSTANT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_BOOLEAN_CONSTANT_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddConstantBoolean : public Transformation {
+ public:
+  explicit TransformationAddConstantBoolean(
+      const protobufs::TransformationAddConstantBoolean& message);
+
+  TransformationAddConstantBoolean(uint32_t fresh_id, bool is_true);
+
+  // - |message_.fresh_id| must not be used by the module.
+  // - The module must already contain OpTypeBool.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - Adds OpConstantTrue (OpConstantFalse) to the module with id
+  //   |message_.fresh_id| if |message_.is_true| holds (does not hold).
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddConstantBoolean message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_BOOLEAN_CONSTANT_H_
diff --git a/source/fuzz/transformation_add_constant_scalar.cpp b/source/fuzz/transformation_add_constant_scalar.cpp
new file mode 100644
index 0000000..36af5e0
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_scalar.cpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_constant_scalar.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddConstantScalar::TransformationAddConstantScalar(
+    const spvtools::fuzz::protobufs::TransformationAddConstantScalar& message)
+    : message_(message) {}
+
+TransformationAddConstantScalar::TransformationAddConstantScalar(
+    uint32_t fresh_id, uint32_t type_id, std::vector<uint32_t> words) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_type_id(type_id);
+  for (auto word : words) {
+    message_.add_word(word);
+  }
+}
+
+bool TransformationAddConstantScalar::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The id needs to be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+  // The type id for the scalar must exist and be a type.
+  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  if (!type) {
+    return false;
+  }
+  uint32_t width;
+  if (type->AsFloat()) {
+    width = type->AsFloat()->width();
+  } else if (type->AsInteger()) {
+    width = type->AsInteger()->width();
+  } else {
+    return false;
+  }
+  // The number of words is the integer floor of the width.
+  auto words = (width + 32 - 1) / 32;
+
+  // The number of words provided by the transformation needs to match the
+  // width of the type.
+  return static_cast<uint32_t>(message_.word().size()) == words;
+}
+
+void TransformationAddConstantScalar::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+  opt::Instruction::OperandList operand_list;
+  for (auto word : message_.word()) {
+    operand_list.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {word}});
+  }
+  context->module()->AddGlobalValue(
+      MakeUnique<opt::Instruction>(context, SpvOpConstant, message_.type_id(),
+                                   message_.fresh_id(), operand_list));
+
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddConstantScalar::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_constant_scalar() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_scalar.h b/source/fuzz/transformation_add_constant_scalar.h
new file mode 100644
index 0000000..914cfe6
--- /dev/null
+++ b/source/fuzz/transformation_add_constant_scalar.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_SCALAR_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_SCALAR_H_
+
+#include <vector>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddConstantScalar : public Transformation {
+ public:
+  explicit TransformationAddConstantScalar(
+      const protobufs::TransformationAddConstantScalar& message);
+
+  TransformationAddConstantScalar(uint32_t fresh_id, uint32_t type_id,
+                                  std::vector<uint32_t> words);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - |message_.type_id| must be the id of a floating-point or integer type
+  // - The size of |message_.word| must be compatible with the width of this
+  //   type
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds a new OpConstant instruction with the given type and words.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddConstantScalar message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_SCALAR_H_
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp
new file mode 100644
index 0000000..229dc90
--- /dev/null
+++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -0,0 +1,188 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_dead_break.h"
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/struct_cfg_analysis.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddDeadBreak::TransformationAddDeadBreak(
+    const spvtools::fuzz::protobufs::TransformationAddDeadBreak& message)
+    : message_(message) {}
+
+TransformationAddDeadBreak::TransformationAddDeadBreak(
+    uint32_t from_block, uint32_t to_block, bool break_condition_value,
+    std::vector<uint32_t> phi_id) {
+  message_.set_from_block(from_block);
+  message_.set_to_block(to_block);
+  message_.set_break_condition_value(break_condition_value);
+  for (auto id : phi_id) {
+    message_.add_phi_id(id);
+  }
+}
+
+bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
+    opt::IRContext* context, opt::BasicBlock* bb_from) const {
+  // Look at the structured control flow associated with |from_block| and
+  // check whether it is contained in an appropriate construct with merge id
+  // |to_block| such that a break from |from_block| to |to_block| is legal.
+
+  // There are three legal cases to consider:
+  // (1) |from_block| is a loop header and |to_block| is its merge
+  // (2) |from_block| is a non-header node of a construct, and |to_block|
+  //     is the merge for that construct
+  // (3) |from_block| is a non-header node of a selection construct, and
+  //     |to_block| is the merge for the innermost loop containing
+  //     |from_block|
+  //
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2653) It may be
+  //   possible to be more aggressive in breaking from switch constructs.
+  //
+  // The reason we need to distinguish between cases (1) and (2) is that the
+  // structured CFG analysis does not deem a header to be part of the construct
+  // that it heads.
+
+  // Consider case (1)
+  if (bb_from->IsLoopHeader()) {
+    // Case (1) holds if |to_block| is the merge block for the loop;
+    // otherwise no case holds
+    return bb_from->MergeBlockId() == message_.to_block();
+  }
+
+  // Both cases (2) and (3) require that |from_block| is inside some
+  // structured control flow construct.
+
+  auto containing_construct =
+      context->GetStructuredCFGAnalysis()->ContainingConstruct(
+          message_.from_block());
+  if (!containing_construct) {
+    // |from_block| is not in a construct from which we can break.
+    return false;
+  }
+
+  // Consider case (2)
+  if (message_.to_block() ==
+      context->cfg()->block(containing_construct)->MergeBlockId()) {
+    // This looks like an instance of case (2).
+    // However, the structured CFG analysis regards the continue construct of a
+    // loop as part of the loop, but it is not legal to jump from a loop's
+    // continue construct to the loop's merge (except from the back-edge block),
+    // so we need to check for this case.
+    //
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2577): We do not
+    //  currently allow a dead break from a back edge block, but we could and
+    //  ultimately should.
+    return !fuzzerutil::BlockIsInLoopContinueConstruct(
+        context, message_.from_block(), containing_construct);
+  }
+
+  // Case (3) holds if and only if |to_block| is the merge block for this
+  // innermost loop that contains |from_block|
+  auto containing_loop_header =
+      context->GetStructuredCFGAnalysis()->ContainingLoop(
+          message_.from_block());
+  if (containing_loop_header &&
+      message_.to_block() ==
+          context->cfg()->block(containing_loop_header)->MergeBlockId()) {
+    return !fuzzerutil::BlockIsInLoopContinueConstruct(
+        context, message_.from_block(), containing_loop_header);
+  }
+  return false;
+}
+
+bool TransformationAddDeadBreak::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // First, we check that a constant with the same value as
+  // |message_.break_condition_value| is present.
+  opt::analysis::Bool bool_type;
+  auto registered_bool_type =
+      context->get_type_mgr()->GetRegisteredType(&bool_type);
+  if (!registered_bool_type) {
+    return false;
+  }
+  opt::analysis::BoolConstant bool_constant(registered_bool_type->AsBool(),
+                                            message_.break_condition_value());
+  if (!context->get_constant_mgr()->FindConstant(&bool_constant)) {
+    // The required constant is not present, so the transformation cannot be
+    // applied.
+    return false;
+  }
+
+  // Check that |message_.from_block| and |message_.to_block| really are block
+  // ids
+  opt::BasicBlock* bb_from =
+      fuzzerutil::MaybeFindBlock(context, message_.from_block());
+  if (bb_from == nullptr) {
+    return false;
+  }
+  opt::BasicBlock* bb_to =
+      fuzzerutil::MaybeFindBlock(context, message_.to_block());
+  if (bb_to == nullptr) {
+    return false;
+  }
+
+  // Check that |message_.from_block| ends with an unconditional branch.
+  if (bb_from->terminator()->opcode() != SpvOpBranch) {
+    // The block associated with the id does not end with an unconditional
+    // branch.
+    return false;
+  }
+
+  assert(bb_from != nullptr &&
+         "We should have found a block if this line of code is reached.");
+  assert(
+      bb_from->id() == message_.from_block() &&
+      "The id of the block we found should match the source id for the break.");
+  assert(bb_to != nullptr &&
+         "We should have found a block if this line of code is reached.");
+  assert(
+      bb_to->id() == message_.to_block() &&
+      "The id of the block we found should match the target id for the break.");
+
+  // Check whether the data passed to extend OpPhi instructions is appropriate.
+  if (!fuzzerutil::PhiIdsOkForNewEdge(context, bb_from, bb_to,
+                                      message_.phi_id())) {
+    return false;
+  }
+
+  // Finally, check that adding the break would respect the rules of structured
+  // control flow.
+  return AddingBreakRespectsStructuredControlFlow(context, bb_from);
+}
+
+void TransformationAddDeadBreak::Apply(opt::IRContext* context,
+                                       FactManager* /*unused*/) const {
+  fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
+      context, context->cfg()->block(message_.from_block()),
+      context->cfg()->block(message_.to_block()),
+      message_.break_condition_value(), message_.phi_id());
+
+  // Invalidate all analyses
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddDeadBreak::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_dead_break() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_dead_break.h b/source/fuzz/transformation_add_dead_break.h
new file mode 100644
index 0000000..aeb4dbb
--- /dev/null
+++ b/source/fuzz/transformation_add_dead_break.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BREAK_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BREAK_H_
+
+#include <vector>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddDeadBreak : public Transformation {
+ public:
+  explicit TransformationAddDeadBreak(
+      const protobufs::TransformationAddDeadBreak& message);
+
+  TransformationAddDeadBreak(uint32_t from_block, uint32_t to_block,
+                             bool break_condition_value,
+                             std::vector<uint32_t> phi_id);
+
+  // - |message_.from_block| must be the id of a block a in the given module.
+  // - |message_.to_block| must be the id of a block b in the given module.
+  // - if |message_.break_condition_value| holds (does not hold) then
+  //   OpConstantTrue (OpConstantFalse) must be present in the module
+  // - |message_.phi_ids| must be a list of ids that are all available at
+  //   |message_.from_block|
+  // - a and b must be in the same function.
+  // - b must be a merge block.
+  // - a must end with an unconditional branch to some block c.
+  // - replacing this branch with a conditional branch to b or c, with
+  //   the boolean constant associated with |message_.break_condition_value| as
+  //   the condition, and the ids in |message_.phi_ids| used to extend
+  //   any OpPhi instructions at b as a result of the edge from a, must
+  //   maintain validity of the module.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Replaces the terminator of a with a conditional branch to b or c.
+  // The boolean constant associated with |message_.break_condition_value| is
+  // used as the condition, and the order of b and c is arranged such that
+  // control is guaranteed to jump to c.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Returns true if and only if adding an edge from |bb_from| to
+  // |message_.to_block| respects structured control flow.
+  bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* context,
+                                                opt::BasicBlock* bb_from) const;
+
+  protobufs::TransformationAddDeadBreak message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BREAK_H_
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp
new file mode 100644
index 0000000..e3b3da2
--- /dev/null
+++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_dead_continue.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddDeadContinue::TransformationAddDeadContinue(
+    const spvtools::fuzz::protobufs::TransformationAddDeadContinue& message)
+    : message_(message) {}
+
+TransformationAddDeadContinue::TransformationAddDeadContinue(
+    uint32_t from_block, bool continue_condition_value,
+    std::vector<uint32_t> phi_id) {
+  message_.set_from_block(from_block);
+  message_.set_continue_condition_value(continue_condition_value);
+  for (auto id : phi_id) {
+    message_.add_phi_id(id);
+  }
+}
+
+bool TransformationAddDeadContinue::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // First, we check that a constant with the same value as
+  // |message_.continue_condition_value| is present.
+  opt::analysis::Bool bool_type;
+  auto registered_bool_type =
+      context->get_type_mgr()->GetRegisteredType(&bool_type);
+  if (!registered_bool_type) {
+    return false;
+  }
+  opt::analysis::BoolConstant bool_constant(
+      registered_bool_type->AsBool(), message_.continue_condition_value());
+  if (!context->get_constant_mgr()->FindConstant(&bool_constant)) {
+    // The required constant is not present, so the transformation cannot be
+    // applied.
+    return false;
+  }
+
+  // Check that |message_.from_block| really is a block id.
+  opt::BasicBlock* bb_from =
+      fuzzerutil::MaybeFindBlock(context, message_.from_block());
+  if (bb_from == nullptr) {
+    return false;
+  }
+
+  // Check that |message_.from_block| ends with an unconditional branch.
+  if (bb_from->terminator()->opcode() != SpvOpBranch) {
+    // The block associated with the id does not end with an unconditional
+    // branch.
+    return false;
+  }
+
+  assert(bb_from != nullptr &&
+         "We should have found a block if this line of code is reached.");
+  assert(
+      bb_from->id() == message_.from_block() &&
+      "The id of the block we found should match the source id for the break.");
+
+  // Get the header for the innermost loop containing |message_.from_block|.
+  // Because the structured CFG analysis does not regard a loop header as part
+  // of the loop it heads, we check first whether bb_from is a loop header
+  // before using the structured CFG analysis.
+  auto loop_header = bb_from->IsLoopHeader()
+                         ? message_.from_block()
+                         : context->GetStructuredCFGAnalysis()->ContainingLoop(
+                               message_.from_block());
+  if (!loop_header) {
+    return false;
+  }
+
+  if (fuzzerutil::BlockIsInLoopContinueConstruct(context, message_.from_block(),
+                                                 loop_header)) {
+    // We cannot jump to the continue target from the continue construct.
+    return false;
+  }
+
+  auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId();
+
+  if (context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) {
+    // A branch straight to the continue target that is also a merge block might
+    // break the property that a construct header must dominate its merge block
+    // (if the merge block is reachable).
+    return false;
+  }
+
+  // The transformation is good if and only if the given phi ids are sufficient
+  // to extend relevant OpPhi instructions in the continue block.
+  return fuzzerutil::PhiIdsOkForNewEdge(context, bb_from,
+                                        context->cfg()->block(continue_block),
+                                        message_.phi_id());
+}
+
+void TransformationAddDeadContinue::Apply(opt::IRContext* context,
+                                          FactManager* /*unused*/) const {
+  auto bb_from = context->cfg()->block(message_.from_block());
+  auto continue_block =
+      bb_from->IsLoopHeader()
+          ? bb_from->ContinueBlockId()
+          : context->GetStructuredCFGAnalysis()->LoopContinueBlock(
+                message_.from_block());
+  assert(continue_block &&
+         "Precondition for this transformation requires that "
+         "message_.from_block must be in a loop.");
+  fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
+      context, bb_from, context->cfg()->block(continue_block),
+      message_.continue_condition_value(), message_.phi_id());
+  // Invalidate all analyses
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_dead_continue() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_dead_continue.h b/source/fuzz/transformation_add_dead_continue.h
new file mode 100644
index 0000000..e49e2a8
--- /dev/null
+++ b/source/fuzz/transformation_add_dead_continue.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
+
+#include <vector>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddDeadContinue : public Transformation {
+ public:
+  explicit TransformationAddDeadContinue(
+      const protobufs::TransformationAddDeadContinue& message);
+
+  TransformationAddDeadContinue(uint32_t from_block,
+                                bool continue_condition_value,
+                                std::vector<uint32_t> phi_id);
+
+  // - |message_.from_block| must be the id of a block a in the given module.
+  // - a must be contained in a loop with continue target b
+  // - b must not be the merge block of a selection construct
+  // - if |message_.continue_condition_value| holds (does not hold) then
+  //   OpConstantTrue (OpConstantFalse) must be present in the module
+  // - |message_.phi_ids| must be a list of ids that are all available at
+  //   |message_.from_block|
+  // - a must end with an unconditional branch to some block c.
+  // - replacing this branch with a conditional branch to b or c, with
+  //   the boolean constant associated with |message_.continue_condition_value|
+  //   as the condition, and the ids in |message_.phi_ids| used to extend any
+  //   OpPhi instructions at b as a result of the edge from a, must maintain
+  //   validity of the module.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Replaces the terminator of a with a conditional branch to b or c.
+  // The boolean constant associated with |message_.continue_condition_value| is
+  // used as the condition, and the order of b and c is arranged such that
+  // control is guaranteed to jump to c.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddDeadContinue message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_
diff --git a/source/fuzz/transformation_add_type_boolean.cpp b/source/fuzz/transformation_add_type_boolean.cpp
new file mode 100644
index 0000000..b55028a
--- /dev/null
+++ b/source/fuzz/transformation_add_type_boolean.cpp
@@ -0,0 +1,61 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_boolean.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddTypeBoolean::TransformationAddTypeBoolean(
+    const spvtools::fuzz::protobufs::TransformationAddTypeBoolean& message)
+    : message_(message) {}
+
+TransformationAddTypeBoolean::TransformationAddTypeBoolean(uint32_t fresh_id) {
+  message_.set_fresh_id(fresh_id);
+}
+
+bool TransformationAddTypeBoolean::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Applicable if there is no bool type already declared in the module.
+  opt::analysis::Bool bool_type;
+  return context->get_type_mgr()->GetId(&bool_type) == 0;
+}
+
+void TransformationAddTypeBoolean::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+  opt::Instruction::OperandList empty_operands;
+  context->module()->AddType(MakeUnique<opt::Instruction>(
+      context, SpvOpTypeBool, 0, message_.fresh_id(), empty_operands));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddTypeBoolean::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_type_boolean() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_boolean.h b/source/fuzz/transformation_add_type_boolean.h
new file mode 100644
index 0000000..98c1e63
--- /dev/null
+++ b/source/fuzz/transformation_add_type_boolean.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_BOOLEAN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_BOOLEAN_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddTypeBoolean : public Transformation {
+ public:
+  explicit TransformationAddTypeBoolean(
+      const protobufs::TransformationAddTypeBoolean& message);
+
+  explicit TransformationAddTypeBoolean(uint32_t fresh_id);
+
+  // - |message_.fresh_id| must not be used by the module.
+  // - The module must not yet declare OpTypeBoolean
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds OpTypeBoolean with |message_.fresh_id| as result id.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddTypeBoolean message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_BOOLEAN_H_
diff --git a/source/fuzz/transformation_add_type_float.cpp b/source/fuzz/transformation_add_type_float.cpp
new file mode 100644
index 0000000..d2af5f8
--- /dev/null
+++ b/source/fuzz/transformation_add_type_float.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_float.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddTypeFloat::TransformationAddTypeFloat(uint32_t fresh_id,
+                                                       uint32_t width) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_width(width);
+}
+
+TransformationAddTypeFloat::TransformationAddTypeFloat(
+    const spvtools::fuzz::protobufs::TransformationAddTypeFloat& message)
+    : message_(message) {}
+
+bool TransformationAddTypeFloat::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Applicable if there is no float type with this width already declared in
+  // the module.
+  opt::analysis::Float float_type(message_.width());
+  return context->get_type_mgr()->GetId(&float_type) == 0;
+}
+
+void TransformationAddTypeFloat::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+  opt::Instruction::OperandList width = {
+      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}}};
+  context->module()->AddType(MakeUnique<opt::Instruction>(
+      context, SpvOpTypeFloat, 0, message_.fresh_id(), width));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddTypeFloat::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_type_float() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_float.h b/source/fuzz/transformation_add_type_float.h
new file mode 100644
index 0000000..0fdc831
--- /dev/null
+++ b/source/fuzz/transformation_add_type_float.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FLOAT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FLOAT_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddTypeFloat : public Transformation {
+ public:
+  explicit TransformationAddTypeFloat(
+      const protobufs::TransformationAddTypeFloat& message);
+
+  TransformationAddTypeFloat(uint32_t fresh_id, uint32_t width);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - The module must not contain an OpTypeFloat instruction with width
+  //   |message_.width|
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds an OpTypeFloat instruction to the module with the given width
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddTypeFloat message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FLOAT_H_
diff --git a/source/fuzz/transformation_add_type_int.cpp b/source/fuzz/transformation_add_type_int.cpp
new file mode 100644
index 0000000..6f59270
--- /dev/null
+++ b/source/fuzz/transformation_add_type_int.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_int.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddTypeInt::TransformationAddTypeInt(
+    const spvtools::fuzz::protobufs::TransformationAddTypeInt& message)
+    : message_(message) {}
+
+TransformationAddTypeInt::TransformationAddTypeInt(uint32_t fresh_id,
+                                                   uint32_t width,
+                                                   bool is_signed) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_width(width);
+  message_.set_is_signed(is_signed);
+}
+
+bool TransformationAddTypeInt::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Applicable if there is no int type with this width and signedness already
+  // declared in the module.
+  opt::analysis::Integer int_type(message_.width(), message_.is_signed());
+  return context->get_type_mgr()->GetId(&int_type) == 0;
+}
+
+void TransformationAddTypeInt::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+  opt::Instruction::OperandList in_operands = {
+      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}},
+      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.is_signed() ? 1u : 0u}}};
+  context->module()->AddType(MakeUnique<opt::Instruction>(
+      context, SpvOpTypeInt, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddTypeInt::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_type_int() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_int.h b/source/fuzz/transformation_add_type_int.h
new file mode 100644
index 0000000..86342d0
--- /dev/null
+++ b/source/fuzz/transformation_add_type_int.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_INT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_INT_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddTypeInt : public Transformation {
+ public:
+  explicit TransformationAddTypeInt(
+      const protobufs::TransformationAddTypeInt& message);
+
+  TransformationAddTypeInt(uint32_t fresh_id, uint32_t width, bool is_signed);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - The module must not contain an OpTypeInt instruction with width
+  //   |message_.width| and signedness |message.is_signed|
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds an OpTypeInt instruction to the module with the given width and
+  // signedness.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddTypeInt message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_INT_H_
diff --git a/source/fuzz/transformation_add_type_pointer.cpp b/source/fuzz/transformation_add_type_pointer.cpp
new file mode 100644
index 0000000..426985a
--- /dev/null
+++ b/source/fuzz/transformation_add_type_pointer.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddTypePointer::TransformationAddTypePointer(
+    const spvtools::fuzz::protobufs::TransformationAddTypePointer& message)
+    : message_(message) {}
+
+TransformationAddTypePointer::TransformationAddTypePointer(
+    uint32_t fresh_id, SpvStorageClass storage_class, uint32_t base_type_id) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_storage_class(storage_class);
+  message_.set_base_type_id(base_type_id);
+}
+
+bool TransformationAddTypePointer::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+  // The base type must be known.
+  return context->get_type_mgr()->GetType(message_.base_type_id()) != nullptr;
+}
+
+void TransformationAddTypePointer::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const {
+  // Add the pointer type.
+  opt::Instruction::OperandList in_operands = {
+      {SPV_OPERAND_TYPE_STORAGE_CLASS, {message_.storage_class()}},
+      {SPV_OPERAND_TYPE_ID, {message_.base_type_id()}}};
+  context->module()->AddType(MakeUnique<opt::Instruction>(
+      context, SpvOpTypePointer, 0, message_.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddTypePointer::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_type_pointer() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_pointer.h b/source/fuzz/transformation_add_type_pointer.h
new file mode 100644
index 0000000..2b9ff77
--- /dev/null
+++ b/source/fuzz/transformation_add_type_pointer.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddTypePointer : public Transformation {
+ public:
+  explicit TransformationAddTypePointer(
+      const protobufs::TransformationAddTypePointer& message);
+
+  TransformationAddTypePointer(uint32_t fresh_id, SpvStorageClass storage_class,
+                               uint32_t base_type_id);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - |message_.base_type_id| must be the result id of an OpType[...]
+  // instruction
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds an OpTypePointer instruction with the given storage class and base
+  // type to the module.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddTypePointer message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
new file mode 100644
index 0000000..ebce185
--- /dev/null
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -0,0 +1,109 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_move_block_down.h"
+
+#include "source/opt/basic_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationMoveBlockDown::TransformationMoveBlockDown(
+    const spvtools::fuzz::protobufs::TransformationMoveBlockDown& message)
+    : message_(message) {}
+
+TransformationMoveBlockDown::TransformationMoveBlockDown(uint32_t id) {
+  message_.set_block_id(id);
+}
+
+bool TransformationMoveBlockDown::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // Go through every block in every function, looking for a block whose id
+  // matches that of the block we want to consider moving down.
+  for (auto& function : *context->module()) {
+    for (auto block_it = function.begin(); block_it != function.end();
+         ++block_it) {
+      if (block_it->id() == message_.block_id()) {
+        // We have found a match.
+        if (block_it == function.begin()) {
+          // The block is the first one appearing in the function.  We are not
+          // allowed to move this block down.
+          return false;
+        }
+        // Record the block we would like to consider moving down.
+        opt::BasicBlock* block_matching_id = &*block_it;
+        if (!context->GetDominatorAnalysis(&function)->IsReachable(
+                block_matching_id)) {
+          // The block is not reachable.  We are not allowed to move it down.
+          return false;
+        }
+        // Now see whether there is some block following that block in program
+        // order.
+        ++block_it;
+        if (block_it == function.end()) {
+          // There is no such block; i.e., the block we are considering moving
+          // is the last one in the function.  The transformation thus does not
+          // apply.
+          return false;
+        }
+        opt::BasicBlock* next_block_in_program_order = &*block_it;
+        // We can move the block of interest down if and only if it does not
+        // dominate the block that comes next.
+        return !context->GetDominatorAnalysis(&function)->Dominates(
+            block_matching_id, next_block_in_program_order);
+      }
+    }
+  }
+
+  // We did not find a matching block, so the transformation is not applicable:
+  // there is no relevant block to move.
+  return false;
+}
+
+void TransformationMoveBlockDown::Apply(opt::IRContext* context,
+                                        FactManager* /*unused*/) const {
+  // Go through every block in every function, looking for a block whose id
+  // matches that of the block we want to move down.
+  for (auto& function : *context->module()) {
+    for (auto block_it = function.begin(); block_it != function.end();
+         ++block_it) {
+      if (block_it->id() == message_.block_id()) {
+        ++block_it;
+        assert(block_it != function.end() &&
+               "To be able to move a block down, it needs to have a "
+               "program-order successor.");
+        function.MoveBasicBlockToAfter(message_.block_id(), &*block_it);
+        // It is prudent to invalidate analyses after changing block ordering in
+        // case any of them depend on it, but the ones that definitely do not
+        // depend on ordering can be preserved. These include the following,
+        // which can likely be extended.
+        context->InvalidateAnalysesExceptFor(
+            opt::IRContext::Analysis::kAnalysisDefUse |
+            opt::IRContext::Analysis::kAnalysisDominatorAnalysis);
+
+        return;
+      }
+    }
+  }
+  assert(false && "No block was found to move down.");
+}
+
+protobufs::Transformation TransformationMoveBlockDown::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_move_block_down() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_move_block_down.h b/source/fuzz/transformation_move_block_down.h
new file mode 100644
index 0000000..fd1584a
--- /dev/null
+++ b/source/fuzz/transformation_move_block_down.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationMoveBlockDown : public Transformation {
+ public:
+  explicit TransformationMoveBlockDown(
+      const protobufs::TransformationMoveBlockDown& message);
+
+  explicit TransformationMoveBlockDown(uint32_t id);
+
+  // - |message_.block_id| must be the id of a block b in the given module.
+  // - b must not be the first nor last block appearing, in program order,
+  //   in a function.
+  // - b must not dominate the block that follows it in program order.
+  // - b must be reachable.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // The block with id |message_.block_id| is moved down; i.e. the program order
+  // between it and the block that follows it is swapped.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationMoveBlockDown message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
new file mode 100644
index 0000000..91b4007
--- /dev/null
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
@@ -0,0 +1,295 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+
+#include <cmath>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+
+// Given floating-point values |lhs| and |rhs|, and a floating-point binary
+// operator |binop|, returns true if it is certain that 'lhs binop rhs'
+// evaluates to |required_value|.
+template <typename T>
+bool float_binop_evaluates_to(T lhs, T rhs, SpvOp binop, bool required_value) {
+  // Infinity and NaN values are conservatively treated as out of scope.
+  if (!std::isfinite(lhs) || !std::isfinite(rhs)) {
+    return false;
+  }
+  bool binop_result;
+  // The following captures the binary operators that spirv-fuzz can actually
+  // generate when turning a boolean constant into a binary expression.
+  switch (binop) {
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+      binop_result = (lhs >= rhs);
+      break;
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+      binop_result = (lhs > rhs);
+      break;
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+      binop_result = (lhs <= rhs);
+      break;
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+      binop_result = (lhs < rhs);
+      break;
+    default:
+      return false;
+  }
+  return binop_result == required_value;
+}
+
+// Analogous to 'float_binop_evaluates_to', but for signed int values.
+template <typename T>
+bool signed_int_binop_evaluates_to(T lhs, T rhs, SpvOp binop,
+                                   bool required_value) {
+  bool binop_result;
+  switch (binop) {
+    case SpvOpSGreaterThanEqual:
+      binop_result = (lhs >= rhs);
+      break;
+    case SpvOpSGreaterThan:
+      binop_result = (lhs > rhs);
+      break;
+    case SpvOpSLessThanEqual:
+      binop_result = (lhs <= rhs);
+      break;
+    case SpvOpSLessThan:
+      binop_result = (lhs < rhs);
+      break;
+    default:
+      return false;
+  }
+  return binop_result == required_value;
+}
+
+// Analogous to 'float_binop_evaluates_to', but for unsigned int values.
+template <typename T>
+bool unsigned_int_binop_evaluates_to(T lhs, T rhs, SpvOp binop,
+                                     bool required_value) {
+  bool binop_result;
+  switch (binop) {
+    case SpvOpUGreaterThanEqual:
+      binop_result = (lhs >= rhs);
+      break;
+    case SpvOpUGreaterThan:
+      binop_result = (lhs > rhs);
+      break;
+    case SpvOpULessThanEqual:
+      binop_result = (lhs <= rhs);
+      break;
+    case SpvOpULessThan:
+      binop_result = (lhs < rhs);
+      break;
+    default:
+      return false;
+  }
+  return binop_result == required_value;
+}
+
+}  // namespace
+
+TransformationReplaceBooleanConstantWithConstantBinary::
+    TransformationReplaceBooleanConstantWithConstantBinary(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceBooleanConstantWithConstantBinary& message)
+    : message_(message) {}
+
+TransformationReplaceBooleanConstantWithConstantBinary::
+    TransformationReplaceBooleanConstantWithConstantBinary(
+        const protobufs::IdUseDescriptor& id_use_descriptor, uint32_t lhs_id,
+        uint32_t rhs_id, SpvOp comparison_opcode,
+        uint32_t fresh_id_for_binary_operation) {
+  *message_.mutable_id_use_descriptor() = id_use_descriptor;
+  message_.set_lhs_id(lhs_id);
+  message_.set_rhs_id(rhs_id);
+  message_.set_opcode(comparison_opcode);
+  message_.set_fresh_id_for_binary_operation(fresh_id_for_binary_operation);
+}
+
+bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // The id for the binary result must be fresh
+  if (!fuzzerutil::IsFreshId(context,
+                             message_.fresh_id_for_binary_operation())) {
+    return false;
+  }
+
+  // The used id must be for a boolean constant
+  auto boolean_constant = context->get_def_use_mgr()->GetDef(
+      message_.id_use_descriptor().id_of_interest());
+  if (!boolean_constant) {
+    return false;
+  }
+  if (!(boolean_constant->opcode() == SpvOpConstantFalse ||
+        boolean_constant->opcode() == SpvOpConstantTrue)) {
+    return false;
+  }
+
+  // The left-hand-side id must correspond to a constant instruction.
+  auto lhs_constant_inst =
+      context->get_def_use_mgr()->GetDef(message_.lhs_id());
+  if (!lhs_constant_inst) {
+    return false;
+  }
+  if (lhs_constant_inst->opcode() != SpvOpConstant) {
+    return false;
+  }
+
+  // The right-hand-side id must correspond to a constant instruction.
+  auto rhs_constant_inst =
+      context->get_def_use_mgr()->GetDef(message_.rhs_id());
+  if (!rhs_constant_inst) {
+    return false;
+  }
+  if (rhs_constant_inst->opcode() != SpvOpConstant) {
+    return false;
+  }
+
+  // The left- and right-hand side instructions must have the same type.
+  if (lhs_constant_inst->type_id() != rhs_constant_inst->type_id()) {
+    return false;
+  }
+
+  // The expression 'LHS opcode RHS' must evaluate to the boolean constant.
+  auto lhs_constant =
+      context->get_constant_mgr()->FindDeclaredConstant(message_.lhs_id());
+  auto rhs_constant =
+      context->get_constant_mgr()->FindDeclaredConstant(message_.rhs_id());
+  bool expected_result = (boolean_constant->opcode() == SpvOpConstantTrue);
+
+  const SpvOp binary_opcode = static_cast<SpvOp>(message_.opcode());
+
+  // We consider the floating point, signed and unsigned integer cases
+  // separately.  In each case the logic is very similar.
+  if (lhs_constant->AsFloatConstant()) {
+    assert(rhs_constant->AsFloatConstant() &&
+           "Both constants should be of the same type.");
+    if (lhs_constant->type()->AsFloat()->width() == 32) {
+      if (!float_binop_evaluates_to(lhs_constant->GetFloat(),
+                                    rhs_constant->GetFloat(), binary_opcode,
+                                    expected_result)) {
+        return false;
+      }
+    } else {
+      assert(lhs_constant->type()->AsFloat()->width() == 64);
+      if (!float_binop_evaluates_to(lhs_constant->GetDouble(),
+                                    rhs_constant->GetDouble(), binary_opcode,
+                                    expected_result)) {
+        return false;
+      }
+    }
+  } else {
+    assert(lhs_constant->AsIntConstant() && "Constants should be in or float.");
+    assert(rhs_constant->AsIntConstant() &&
+           "Both constants should be of the same type.");
+    if (lhs_constant->type()->AsInteger()->IsSigned()) {
+      if (lhs_constant->type()->AsInteger()->width() == 32) {
+        if (!signed_int_binop_evaluates_to(lhs_constant->GetS32(),
+                                           rhs_constant->GetS32(),
+                                           binary_opcode, expected_result)) {
+          return false;
+        }
+      } else {
+        assert(lhs_constant->type()->AsInteger()->width() == 64);
+        if (!signed_int_binop_evaluates_to(lhs_constant->GetS64(),
+                                           rhs_constant->GetS64(),
+                                           binary_opcode, expected_result)) {
+          return false;
+        }
+      }
+    } else {
+      if (lhs_constant->type()->AsInteger()->width() == 32) {
+        if (!unsigned_int_binop_evaluates_to(lhs_constant->GetU32(),
+                                             rhs_constant->GetU32(),
+                                             binary_opcode, expected_result)) {
+          return false;
+        }
+      } else {
+        assert(lhs_constant->type()->AsInteger()->width() == 64);
+        if (!unsigned_int_binop_evaluates_to(lhs_constant->GetU64(),
+                                             rhs_constant->GetU64(),
+                                             binary_opcode, expected_result)) {
+          return false;
+        }
+      }
+    }
+  }
+
+  // The id use descriptor must identify some instruction
+  return transformation::FindInstruction(message_.id_use_descriptor(),
+                                         context) != nullptr;
+}
+
+void TransformationReplaceBooleanConstantWithConstantBinary::Apply(
+    opt::IRContext* context, FactManager* fact_manager) const {
+  ApplyWithResult(context, fact_manager);
+}
+
+opt::Instruction*
+TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
+    opt::IRContext* context, FactManager* /*unused*/) const {
+  opt::analysis::Bool bool_type;
+  opt::Instruction::OperandList operands = {
+      {SPV_OPERAND_TYPE_ID, {message_.lhs_id()}},
+      {SPV_OPERAND_TYPE_ID, {message_.rhs_id()}}};
+  auto binary_instruction = MakeUnique<opt::Instruction>(
+      context, static_cast<SpvOp>(message_.opcode()),
+      context->get_type_mgr()->GetId(&bool_type),
+      message_.fresh_id_for_binary_operation(), operands);
+  opt::Instruction* result = binary_instruction.get();
+  auto instruction_containing_constant_use =
+      transformation::FindInstruction(message_.id_use_descriptor(), context);
+
+  // We want to insert the new instruction before the instruction that contains
+  // the use of the boolean, but we need to go backwards one more instruction if
+  // the using instruction is preceded by a merge instruction.
+  auto instruction_before_which_to_insert = instruction_containing_constant_use;
+  {
+    opt::Instruction* previous_node =
+        instruction_before_which_to_insert->PreviousNode();
+    if (previous_node && (previous_node->opcode() == SpvOpLoopMerge ||
+                          previous_node->opcode() == SpvOpSelectionMerge)) {
+      instruction_before_which_to_insert = previous_node;
+    }
+  }
+  instruction_before_which_to_insert->InsertBefore(
+      std::move(binary_instruction));
+  instruction_containing_constant_use->SetInOperand(
+      message_.id_use_descriptor().in_operand_index(),
+      {message_.fresh_id_for_binary_operation()});
+  fuzzerutil::UpdateModuleIdBound(context,
+                                  message_.fresh_id_for_binary_operation());
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+  return result;
+}
+
+protobufs::Transformation
+TransformationReplaceBooleanConstantWithConstantBinary::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_boolean_constant_with_constant_binary() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
new file mode 100644
index 0000000..c384093
--- /dev/null
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_BOOLEAN_CONSTANT_WITH_CONSTANT_BINARY_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_BOOLEAN_CONSTANT_WITH_CONSTANT_BINARY_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceBooleanConstantWithConstantBinary
+    : public Transformation {
+ public:
+  explicit TransformationReplaceBooleanConstantWithConstantBinary(
+      const protobufs::TransformationReplaceBooleanConstantWithConstantBinary&
+          message);
+
+  TransformationReplaceBooleanConstantWithConstantBinary(
+      const protobufs::IdUseDescriptor& id_use_descriptor, uint32_t lhs_id,
+      uint32_t rhs_id, SpvOp comparison_opcode,
+      uint32_t fresh_id_for_binary_operation);
+
+  // - |message_.fresh_id_for_binary_operation| must not already be used by the
+  //   module.
+  // - |message_.id_use_descriptor| must identify a use of a boolean constant c.
+  // - |message_.lhs_id| and |message.rhs_id| must be the ids of constant
+  //   instructions with the same type
+  // - |message_.opcode| must be suitable for applying to |message.lhs_id| and
+  //   |message_.rhs_id|, and the result must evaluate to the boolean constant
+  //   c.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // A new instruction is added before the boolean constant usage that computes
+  // the result of applying |message_.opcode| to |message_.lhs_id| and
+  // |message_.rhs_id| is added, with result id
+  // |message_.fresh_id_for_binary_operation|.  The boolean constant usage is
+  // replaced with this result id.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  // The same as Apply, except that the newly-added binary instruction is
+  // returned.
+  opt::Instruction* ApplyWithResult(opt::IRContext* context,
+                                    FactManager* fact_manager) const;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationReplaceBooleanConstantWithConstantBinary message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_BOOLEAN_CONSTANT_WITH_CONSTANT_BINARY_H_
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
new file mode 100644
index 0000000..48334ba
--- /dev/null
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -0,0 +1,235 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceConstantWithUniform::
+    TransformationReplaceConstantWithUniform(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceConstantWithUniform& message)
+    : message_(message) {}
+
+TransformationReplaceConstantWithUniform::
+    TransformationReplaceConstantWithUniform(
+        protobufs::IdUseDescriptor id_use,
+        protobufs::UniformBufferElementDescriptor uniform_descriptor,
+        uint32_t fresh_id_for_access_chain, uint32_t fresh_id_for_load) {
+  *message_.mutable_id_use_descriptor() = std::move(id_use);
+  *message_.mutable_uniform_descriptor() = std::move(uniform_descriptor);
+  message_.set_fresh_id_for_access_chain(fresh_id_for_access_chain);
+  message_.set_fresh_id_for_load(fresh_id_for_load);
+}
+
+std::unique_ptr<opt::Instruction>
+TransformationReplaceConstantWithUniform::MakeAccessChainInstruction(
+    spvtools::opt::IRContext* context, uint32_t constant_type_id) const {
+  // The input operands for the access chain.
+  opt::Instruction::OperandList operands_for_access_chain;
+
+  opt::Instruction* uniform_variable =
+      FindUniformVariable(message_.uniform_descriptor(), context, false);
+
+  // The first input operand is the id of the uniform variable.
+  operands_for_access_chain.push_back(
+      {SPV_OPERAND_TYPE_ID, {uniform_variable->result_id()}});
+
+  // The other input operands are the ids of the constants used to index into
+  // the uniform. The uniform buffer descriptor specifies a series of literals;
+  // for each we find the id of the instruction that defines it, and add these
+  // instruction ids as operands.
+  opt::analysis::Integer int_type(32, true);
+  auto registered_int_type =
+      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+  for (auto index : message_.uniform_descriptor().index()) {
+    opt::analysis::IntConstant int_constant(registered_int_type, {index});
+    auto constant_id = context->get_constant_mgr()->FindDeclaredConstant(
+        &int_constant, int_type_id);
+    operands_for_access_chain.push_back({SPV_OPERAND_TYPE_ID, {constant_id}});
+  }
+
+  // The type id for the access chain is a uniform pointer with base type
+  // matching the given constant id type.
+  auto type_and_pointer_type = context->get_type_mgr()->GetTypeAndPointerType(
+      constant_type_id, SpvStorageClassUniform);
+  assert(type_and_pointer_type.first != nullptr);
+  assert(type_and_pointer_type.second != nullptr);
+  auto pointer_to_uniform_constant_type_id =
+      context->get_type_mgr()->GetId(type_and_pointer_type.second.get());
+
+  return MakeUnique<opt::Instruction>(
+      context, SpvOpAccessChain, pointer_to_uniform_constant_type_id,
+      message_.fresh_id_for_access_chain(), operands_for_access_chain);
+}
+
+std::unique_ptr<opt::Instruction>
+TransformationReplaceConstantWithUniform::MakeLoadInstruction(
+    spvtools::opt::IRContext* context, uint32_t constant_type_id) const {
+  opt::Instruction::OperandList operands_for_load = {
+      {SPV_OPERAND_TYPE_ID, {message_.fresh_id_for_access_chain()}}};
+  return MakeUnique<opt::Instruction>(context, SpvOpLoad, constant_type_id,
+                                      message_.fresh_id_for_load(),
+                                      operands_for_load);
+}
+
+bool TransformationReplaceConstantWithUniform::IsApplicable(
+    spvtools::opt::IRContext* context,
+    const spvtools::fuzz::FactManager& fact_manager) const {
+  // The following is really an invariant of the transformation rather than
+  // merely a requirement of the precondition.  We check it here since we cannot
+  // check it in the message_ constructor.
+  assert(message_.fresh_id_for_access_chain() != message_.fresh_id_for_load() &&
+         "Fresh ids for access chain and load result cannot be the same.");
+
+  // The ids for the access chain and load instructions must both be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_access_chain())) {
+    return false;
+  }
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_load())) {
+    return false;
+  }
+
+  // The id specified in the id use descriptor must be that of a declared scalar
+  // constant.
+  auto declared_constant = context->get_constant_mgr()->FindDeclaredConstant(
+      message_.id_use_descriptor().id_of_interest());
+  if (!declared_constant) {
+    return false;
+  }
+  if (!declared_constant->AsScalarConstant()) {
+    return false;
+  }
+
+  // The fact manager needs to believe that the uniform data element described
+  // by the uniform buffer element descriptor will hold a scalar value.
+  auto constant_id_associated_with_uniform =
+      fact_manager.GetConstantFromUniformDescriptor(
+          context, message_.uniform_descriptor());
+  if (!constant_id_associated_with_uniform) {
+    return false;
+  }
+  auto constant_associated_with_uniform =
+      context->get_constant_mgr()->FindDeclaredConstant(
+          constant_id_associated_with_uniform);
+  assert(constant_associated_with_uniform &&
+         "The constant should be present in the module.");
+  if (!constant_associated_with_uniform->AsScalarConstant()) {
+    return false;
+  }
+
+  // The types and values of the scalar value held in the id specified by the id
+  // use descriptor and in the uniform data element specified by the uniform
+  // buffer element descriptor need to match on both type and value.
+  if (!declared_constant->type()->IsSame(
+          constant_associated_with_uniform->type())) {
+    return false;
+  }
+  if (declared_constant->AsScalarConstant()->words() !=
+      constant_associated_with_uniform->AsScalarConstant()->words()) {
+    return false;
+  }
+
+  // The id use descriptor must identify some instruction with respect to the
+  // module.
+  auto instruction_using_constant =
+      transformation::FindInstruction(message_.id_use_descriptor(), context);
+  if (!instruction_using_constant) {
+    return false;
+  }
+
+  // The module needs to have a uniform pointer type suitable for indexing into
+  // the uniform variable, i.e. matching the type of the constant we wish to
+  // replace with a uniform.
+  opt::analysis::Pointer pointer_to_type_of_constant(declared_constant->type(),
+                                                     SpvStorageClassUniform);
+  if (!context->get_type_mgr()->GetId(&pointer_to_type_of_constant)) {
+    return false;
+  }
+
+  // In order to index into the uniform, the module has got to contain the int32
+  // type, plus an OpConstant for each of the indices of interest.
+  opt::analysis::Integer int_type(32, true);
+  if (!context->get_type_mgr()->GetId(&int_type)) {
+    return false;
+  }
+  auto registered_int_type =
+      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+  for (auto index : message_.uniform_descriptor().index()) {
+    opt::analysis::IntConstant int_constant(registered_int_type, {index});
+    if (!context->get_constant_mgr()->FindDeclaredConstant(&int_constant,
+                                                           int_type_id)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationReplaceConstantWithUniform::Apply(
+    spvtools::opt::IRContext* context,
+    spvtools::fuzz::FactManager* /*unused*/) const {
+  // Get the instruction that contains the id use we wish to replace.
+  auto instruction_containing_constant_use =
+      transformation::FindInstruction(message_.id_use_descriptor(), context);
+  assert(instruction_containing_constant_use &&
+         "Precondition requires that the id use can be found.");
+  assert(instruction_containing_constant_use->GetSingleWordInOperand(
+             message_.id_use_descriptor().in_operand_index()) ==
+             message_.id_use_descriptor().id_of_interest() &&
+         "Does not appear to be a usage of the desired id.");
+
+  // The id of the type for the constant whose use we wish to replace.
+  auto constant_type_id =
+      context->get_def_use_mgr()
+          ->GetDef(message_.id_use_descriptor().id_of_interest())
+          ->type_id();
+
+  // Add an access chain instruction to target the uniform element.
+  instruction_containing_constant_use->InsertBefore(
+      MakeAccessChainInstruction(context, constant_type_id));
+
+  // Add a load from this access chain.
+  instruction_containing_constant_use->InsertBefore(
+      MakeLoadInstruction(context, constant_type_id));
+
+  // Adjust the instruction containing the usage of the constant so that this
+  // usage refers instead to the result of the load.
+  instruction_containing_constant_use->SetInOperand(
+      message_.id_use_descriptor().in_operand_index(),
+      {message_.fresh_id_for_load()});
+
+  // Update the module id bound to reflect the new instructions.
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id_for_load());
+  fuzzerutil::UpdateModuleIdBound(context,
+                                  message_.fresh_id_for_access_chain());
+
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationReplaceConstantWithUniform::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_replace_constant_with_uniform() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h
new file mode 100644
index 0000000..ed354b1
--- /dev/null
+++ b/source/fuzz/transformation_replace_constant_with_uniform.h
@@ -0,0 +1,91 @@
+#include <utility>
+
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceConstantWithUniform : public Transformation {
+ public:
+  explicit TransformationReplaceConstantWithUniform(
+      const protobufs::TransformationReplaceConstantWithUniform& message);
+
+  TransformationReplaceConstantWithUniform(
+      protobufs::IdUseDescriptor id_use,
+      protobufs::UniformBufferElementDescriptor uniform_descriptor,
+      uint32_t fresh_id_for_access_chain, uint32_t fresh_id_for_load);
+
+  // - |message_.fresh_id_for_access_chain| and |message_.fresh_id_for_load|
+  //   must be distinct fresh ids.
+  // - |message_.uniform_descriptor| specifies a result id and a list of integer
+  //   literal indices.
+  //   As an example, suppose |message_.uniform_descriptor| is (18, [0, 1, 0])
+  //   It is required that:
+  //     - the result id (18 in our example) is the id of some uniform variable
+  //     - the module contains an integer constant instruction corresponding to
+  //       each of the literal indices; in our example there must thus be
+  //       OpConstant instructions %A and %B say for each of 0 and 1
+  //     - it is legitimate to index into the uniform variable using the
+  //       sequence of indices; in our example this means indexing into %18
+  //       using the sequence %A %B %A
+  //     - the module contains a uniform pointer type corresponding to the type
+  //       of the uniform data element obtained by following these indices
+  // - |message_.id_use_descriptor| identifies the use of some id %C.  It is
+  //   required that:
+  //     - this use does indeed exist in the module
+  //     - %C is an OpConstant
+  //     - According to the fact manager, the uniform data element specified by
+  //       |message_.uniform_descriptor| holds a value with the same type and
+  //       value as %C
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - Introduces two new instructions:
+  //   - An access chain targeting the uniform data element specified by
+  //     |message_.uniform_descriptor|, with result id
+  //     |message_.fresh_id_for_access_chain|
+  //   - A load from this access chain, with id |message_.fresh_id_for_load|
+  // - Replaces the id use specified by |message_.id_use_descriptor| with
+  //   |message_.fresh_id_for_load|
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Helper method to create an access chain for the uniform element associated
+  // with the transformation.
+  std::unique_ptr<opt::Instruction> MakeAccessChainInstruction(
+      spvtools::opt::IRContext* context, uint32_t constant_type_id) const;
+
+  // Helper to create a load instruction.
+  std::unique_ptr<opt::Instruction> MakeLoadInstruction(
+      spvtools::opt::IRContext* context, uint32_t constant_type_id) const;
+
+  protobufs::TransformationReplaceConstantWithUniform message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
new file mode 100644
index 0000000..a8c33de
--- /dev/null
+++ b/source/fuzz/transformation_split_block.cpp
@@ -0,0 +1,179 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_split_block.h"
+
+#include <utility>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSplitBlock::TransformationSplitBlock(
+    const spvtools::fuzz::protobufs::TransformationSplitBlock& message)
+    : message_(message) {}
+
+TransformationSplitBlock::TransformationSplitBlock(uint32_t result_id,
+                                                   uint32_t offset,
+                                                   uint32_t fresh_id) {
+  message_.set_result_id(result_id);
+  message_.set_offset(offset);
+  message_.set_fresh_id(fresh_id);
+}
+
+std::pair<bool, opt::BasicBlock::iterator>
+TransformationSplitBlock::FindInstToSplitBefore(opt::BasicBlock* block) const {
+  // There are three possibilities:
+  // (1) the transformation wants to split at some offset from the block's
+  //     label.
+  // (2) the transformation wants to split at some offset from a
+  //     non-label instruction inside the block.
+  // (3) the split assocaiated with this transformation has nothing to do with
+  //     this block
+  if (message_.result_id() == block->id()) {
+    // Case (1).
+    if (message_.offset() == 0) {
+      // The offset is not allowed to be 0: this would mean splitting before the
+      // block's label.
+      // By returning (true, block->end()), we indicate that we did find the
+      // instruction (so that it is not worth searching further for it), but
+      // that splitting will not be possible.
+      return {true, block->end()};
+    }
+    // Conceptually, the first instruction in the block is [label + 1].
+    // We thus start from 1 when applying the offset.
+    auto inst_it = block->begin();
+    for (uint32_t i = 1; i < message_.offset() && inst_it != block->end();
+         i++) {
+      ++inst_it;
+    }
+    // This is either the desired instruction, or the end of the block.
+    return {true, inst_it};
+  }
+  for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
+    if (message_.result_id() == inst_it->result_id()) {
+      // Case (2): we have found the base instruction; we now apply the offset.
+      for (uint32_t i = 0; i < message_.offset() && inst_it != block->end();
+           i++) {
+        ++inst_it;
+      }
+      // This is either the desired instruction, or the end of the block.
+      return {true, inst_it};
+    }
+  }
+  // Case (3).
+  return {false, block->end()};
+}
+
+bool TransformationSplitBlock::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    // We require the id for the new block to be unused.
+    return false;
+  }
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto maybe_split_before = FindInstToSplitBefore(&block);
+      if (!maybe_split_before.first) {
+        continue;
+      }
+      if (maybe_split_before.second == block.end()) {
+        // The base instruction was found, but the offset was inappropriate.
+        return false;
+      }
+      if (block.IsLoopHeader()) {
+        // We cannot split a loop header block: back-edges would become invalid.
+        return false;
+      }
+      auto split_before = maybe_split_before.second;
+      if (split_before->PreviousNode() &&
+          split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
+        // We cannot split directly after a selection merge: this would separate
+        // the merge from its associated branch or switch operation.
+        return false;
+      }
+      if (split_before->opcode() == SpvOpVariable) {
+        // We cannot split directly after a variable; variables in a function
+        // must be contiguous in the entry block.
+        return false;
+      }
+      if (split_before->opcode() == SpvOpPhi &&
+          split_before->NumInOperands() != 2) {
+        // We cannot split before an OpPhi unless the OpPhi has exactly one
+        // associated incoming edge.
+        return false;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+void TransformationSplitBlock::Apply(opt::IRContext* context,
+                                     FactManager* /*unused*/) const {
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto maybe_split_before = FindInstToSplitBefore(&block);
+      if (!maybe_split_before.first) {
+        continue;
+      }
+      assert(maybe_split_before.second != block.end() &&
+             "If the transformation is applicable, we should have an "
+             "instruction to split on.");
+      // We need to make sure the module's id bound is large enough to add the
+      // fresh id.
+      fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+      // Split the block.
+      auto new_bb = block.SplitBasicBlock(context, message_.fresh_id(),
+                                          maybe_split_before.second);
+      // The split does not automatically add a branch between the two parts of
+      // the original block, so we add one.
+      block.AddInstruction(MakeUnique<opt::Instruction>(
+          context, SpvOpBranch, 0, 0,
+          std::initializer_list<opt::Operand>{
+              opt::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+                           {message_.fresh_id()})}));
+      // If we split before OpPhi instructions, we need to update their
+      // predecessor operand so that the block they used to be inside is now the
+      // predecessor.
+      new_bb->ForEachPhiInst([&block](opt::Instruction* phi_inst) {
+        // The following assertion is a sanity check.  It is guaranteed to hold
+        // if IsApplicable holds.
+        assert(phi_inst->NumInOperands() == 2 &&
+               "We can only split a block before an OpPhi if block has exactly "
+               "one predecessor.");
+        phi_inst->SetInOperand(1, {block.id()});
+      });
+      // Invalidate all analyses
+      context->InvalidateAnalysesExceptFor(
+          opt::IRContext::Analysis::kAnalysisNone);
+      return;
+    }
+  }
+  assert(0 &&
+         "Should be unreachable: it should have been possible to apply this "
+         "transformation.");
+}
+
+protobufs::Transformation TransformationSplitBlock::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_split_block() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
new file mode 100644
index 0000000..ef4aa75
--- /dev/null
+++ b/source/fuzz/transformation_split_block.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationSplitBlock : public Transformation {
+ public:
+  explicit TransformationSplitBlock(
+      const protobufs::TransformationSplitBlock& message);
+
+  TransformationSplitBlock(uint32_t result_id, uint32_t offset,
+                           uint32_t fresh_id);
+
+  // - |message_.result_id| must be the result id of an instruction 'base' in
+  //   some block 'blk'.
+  // - 'blk' must contain an instruction 'inst' located |message_.offset|
+  //   instructions after 'inst' (if |message_.offset| = 0 then 'inst' =
+  //   'base').
+  // - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
+  //   appear in a new block that 'blk' directly jumps to must be valid.
+  // - |message_.fresh_id| must not be used by the module.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - A new block with label |message_.fresh_id| is inserted right after 'blk'
+  //   in program order.
+  // - All instructions of 'blk' from 'inst' onwards are moved into the new
+  //   block.
+  // - 'blk' is made to jump unconditionally to the new block.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Returns:
+  // - (true, block->end()) if the relevant instruction is in this block
+  //      but inapplicable
+  // - (true, it) if 'it' is an iterator for the relevant instruction
+  // - (false, _) otherwise.
+  std::pair<bool, opt::BasicBlock::iterator> FindInstToSplitBefore(
+      opt::BasicBlock* block) const;
+
+  protobufs::TransformationSplitBlock message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
diff --git a/source/fuzz/uniform_buffer_element_descriptor.cpp b/source/fuzz/uniform_buffer_element_descriptor.cpp
new file mode 100644
index 0000000..8c758e4
--- /dev/null
+++ b/source/fuzz/uniform_buffer_element_descriptor.cpp
@@ -0,0 +1,118 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+
+#include <source/opt/instruction.h>
+
+namespace spvtools {
+namespace fuzz {
+
+protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
+    uint32_t descriptor_set, uint32_t binding,
+    std::vector<uint32_t>&& indices) {
+  protobufs::UniformBufferElementDescriptor result;
+  result.set_descriptor_set(descriptor_set);
+  result.set_binding(binding);
+  for (auto index : indices) {
+    result.add_index(index);
+  }
+  return result;
+}
+
+bool UniformBufferElementDescriptorEquals::operator()(
+    const protobufs::UniformBufferElementDescriptor* first,
+    const protobufs::UniformBufferElementDescriptor* second) const {
+  return first->descriptor_set() == second->descriptor_set() &&
+         first->binding() == second->binding() &&
+         first->index().size() == second->index().size() &&
+         std::equal(first->index().begin(), first->index().end(),
+                    second->index().begin());
+}
+
+opt::Instruction* FindUniformVariable(
+    const protobufs::UniformBufferElementDescriptor&
+        uniform_buffer_element_descriptor,
+    opt::IRContext* context, bool check_unique) {
+  opt::Instruction* result = nullptr;
+
+  for (auto& inst : context->types_values()) {
+    // Consider all global variables with uniform storage class.
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (inst.GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+      continue;
+    }
+
+    // Determine whether the variable is decorated with a descriptor set
+    // matching that in |uniform_buffer_element|.
+    bool descriptor_set_matches = false;
+    context->get_decoration_mgr()->ForEachDecoration(
+        inst.result_id(), SpvDecorationDescriptorSet,
+        [&descriptor_set_matches, &uniform_buffer_element_descriptor](
+            const opt::Instruction& decoration_inst) {
+          const uint32_t kDescriptorSetOperandIndex = 2;
+          if (decoration_inst.GetSingleWordInOperand(
+                  kDescriptorSetOperandIndex) ==
+              uniform_buffer_element_descriptor.descriptor_set()) {
+            descriptor_set_matches = true;
+          }
+        });
+    if (!descriptor_set_matches) {
+      // Descriptor set does not match.
+      continue;
+    }
+
+    // Determine whether the variable is decorated with a binding matching that
+    // in |uniform_buffer_element|.
+    bool binding_matches = false;
+    context->get_decoration_mgr()->ForEachDecoration(
+        inst.result_id(), SpvDecorationBinding,
+        [&binding_matches, &uniform_buffer_element_descriptor](
+            const opt::Instruction& decoration_inst) {
+          const uint32_t kBindingOperandIndex = 2;
+          if (decoration_inst.GetSingleWordInOperand(kBindingOperandIndex) ==
+              uniform_buffer_element_descriptor.binding()) {
+            binding_matches = true;
+          }
+        });
+    if (!binding_matches) {
+      // Binding does not match.
+      continue;
+    }
+
+    // This instruction is a uniform variable with the right descriptor set and
+    // binding.
+    if (!check_unique) {
+      // If we aren't checking uniqueness, return it.
+      return &inst;
+    }
+
+    if (result) {
+      // More than one uniform variable is decorated with the given descriptor
+      // set and binding. This means the fact is ambiguous.
+      return nullptr;
+    }
+    result = &inst;
+  }
+
+  // We get here either if no match was found, or if |check_unique| holds and
+  // exactly one match was found.
+  assert(result == nullptr || check_unique);
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/uniform_buffer_element_descriptor.h b/source/fuzz/uniform_buffer_element_descriptor.h
new file mode 100644
index 0000000..23a16f0
--- /dev/null
+++ b/source/fuzz/uniform_buffer_element_descriptor.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
+#define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Factory method to create a uniform buffer element descriptor message from an
+// id and list of indices.
+protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
+    uint32_t descriptor_set, uint32_t binding, std::vector<uint32_t>&& indices);
+
+// Equality function for uniform buffer element descriptors.
+struct UniformBufferElementDescriptorEquals {
+  bool operator()(
+      const protobufs::UniformBufferElementDescriptor* first,
+      const protobufs::UniformBufferElementDescriptor* second) const;
+};
+
+// Returns a pointer to an OpVariable in |context| that is decorated with the
+// descriptor set and binding associated with |uniform_buffer_element|.  Returns
+// nullptr if no such variable exists.  If multiple such variables exist, a
+// pointer to an arbitrary one of the associated instructions is returned if
+// |check_unique| is false, and nullptr is returned if |check_unique| is true.
+opt::Instruction* FindUniformVariable(
+    const protobufs::UniformBufferElementDescriptor&
+        uniform_buffer_element_descriptor,
+    opt::IRContext* context, bool check_unique);
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
diff --git a/source/id_descriptor.cpp b/source/id_descriptor.cpp
deleted file mode 100644
index d44ed67..0000000
--- a/source/id_descriptor.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-// 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 "source/id_descriptor.h"
-
-#include <cassert>
-#include <iostream>
-
-#include "source/opcode.h"
-#include "source/operand.h"
-
-namespace spvtools {
-namespace {
-
-// Hashes an array of words. Order of words is important.
-uint32_t HashU32Array(const std::vector<uint32_t>& words) {
-  // The hash function is a sum of hashes of each word seeded by word index.
-  // Knuth's multiplicative hash is used to hash the words.
-  const uint32_t kKnuthMulHash = 2654435761;
-  uint32_t val = 0;
-  for (uint32_t i = 0; i < words.size(); ++i) {
-    val += (words[i] + i + 123) * kKnuthMulHash;
-  }
-  return val;
-}
-
-}  // namespace
-
-uint32_t IdDescriptorCollection::ProcessInstruction(
-    const spv_parsed_instruction_t& inst) {
-  if (!inst.result_id) return 0;
-
-  assert(words_.empty());
-  words_.push_back(inst.words[0]);
-
-  for (size_t operand_index = 0; operand_index < inst.num_operands;
-       ++operand_index) {
-    const auto& operand = inst.operands[operand_index];
-    if (spvIsIdType(operand.type)) {
-      const uint32_t id = inst.words[operand.offset];
-      const auto it = id_to_descriptor_.find(id);
-      // Forward declared ids are not hashed.
-      if (it != id_to_descriptor_.end()) {
-        words_.push_back(it->second);
-      }
-    } else {
-      for (size_t operand_word_index = 0;
-           operand_word_index < operand.num_words; ++operand_word_index) {
-        words_.push_back(inst.words[operand.offset + operand_word_index]);
-      }
-    }
-  }
-
-  uint32_t descriptor =
-      custom_hash_func_ ? custom_hash_func_(words_) : HashU32Array(words_);
-  if (descriptor == 0) descriptor = 1;
-  assert(descriptor);
-
-  words_.clear();
-
-  const auto result = id_to_descriptor_.emplace(inst.result_id, descriptor);
-  assert(result.second);
-  (void)result;
-  return descriptor;
-}
-
-}  // namespace spvtools
diff --git a/source/id_descriptor.h b/source/id_descriptor.h
deleted file mode 100644
index add2334..0000000
--- a/source/id_descriptor.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// 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.
-
-#ifndef SOURCE_ID_DESCRIPTOR_H_
-#define SOURCE_ID_DESCRIPTOR_H_
-
-#include <unordered_map>
-#include <vector>
-
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-
-using CustomHashFunc = std::function<uint32_t(const std::vector<uint32_t>&)>;
-
-// Computes and stores id descriptors.
-//
-// Descriptors are computed as hash of all words in the instruction where ids
-// were substituted with previously computed descriptors.
-class IdDescriptorCollection {
- public:
-  explicit IdDescriptorCollection(
-      CustomHashFunc custom_hash_func = CustomHashFunc())
-      : custom_hash_func_(custom_hash_func) {
-    words_.reserve(16);
-  }
-
-  // Computes descriptor for the result id of the given instruction and
-  // registers it in id_to_descriptor_. Returns the computed descriptor.
-  // This function needs to be sequentially called for every instruction in the
-  // module.
-  uint32_t ProcessInstruction(const spv_parsed_instruction_t& inst);
-
-  // Returns a previously computed descriptor id.
-  uint32_t GetDescriptor(uint32_t id) const {
-    const auto it = id_to_descriptor_.find(id);
-    if (it == id_to_descriptor_.end()) return 0;
-    return it->second;
-  }
-
- private:
-  std::unordered_map<uint32_t, uint32_t> id_to_descriptor_;
-
-  std::function<uint32_t(const std::vector<uint32_t>&)> custom_hash_func_;
-
-  // Scratch buffer used for hashing. Class member to optimize on allocation.
-  std::vector<uint32_t> words_;
-};
-
-}  // namespace spvtools
-
-#endif  // SOURCE_ID_DESCRIPTOR_H_
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index b5fe897..a1ed11d 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -128,4 +128,6 @@
   return valid;
 }
 
+bool SpirvTools::IsValid() const { return impl_->context != nullptr; }
+
 }  // namespace spvtools
diff --git a/source/link/linker.cpp b/source/link/linker.cpp
index f28b759..d99a1e8 100644
--- a/source/link/linker.cpp
+++ b/source/link/linker.cpp
@@ -33,6 +33,7 @@
 #include "source/opt/ir_loader.h"
 #include "source/opt/pass_manager.h"
 #include "source/opt/remove_duplicates_pass.h"
+#include "source/opt/type_manager.h"
 #include "source/spirv_target_env.h"
 #include "source/util/make_unique.h"
 #include "spirv-tools/libspirv.hpp"
@@ -40,14 +41,15 @@
 namespace spvtools {
 namespace {
 
-using opt::IRContext;
 using opt::Instruction;
+using opt::IRContext;
 using opt::Module;
-using opt::Operand;
 using opt::PassManager;
 using opt::RemoveDuplicatesPass;
 using opt::analysis::DecorationManager;
 using opt::analysis::DefUseManager;
+using opt::analysis::Type;
+using opt::analysis::TypeManager;
 
 // Stores various information about an imported or exported symbol.
 struct LinkageSymbolInfo {
@@ -472,14 +474,15 @@
                                             opt::IRContext* context) {
   spv_position_t position = {};
 
-  // Ensure th import and export types are the same.
-  const DefUseManager& def_use_manager = *context->get_def_use_mgr();
+  // Ensure the import and export types are the same.
   const DecorationManager& decoration_manager = *context->get_decoration_mgr();
+  const TypeManager& type_manager = *context->get_type_mgr();
   for (const auto& linking_entry : linkings_to_do) {
-    if (!RemoveDuplicatesPass::AreTypesEqual(
-            *def_use_manager.GetDef(linking_entry.imported_symbol.type_id),
-            *def_use_manager.GetDef(linking_entry.exported_symbol.type_id),
-            context))
+    Type* imported_symbol_type =
+        type_manager.GetType(linking_entry.imported_symbol.type_id);
+    Type* exported_symbol_type =
+        type_manager.GetType(linking_entry.exported_symbol.type_id);
+    if (!(*imported_symbol_type == *exported_symbol_type))
       return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
              << "Type mismatch on symbol \""
              << linking_entry.imported_symbol.name
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 78c2386..7f91a0f 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -97,6 +97,7 @@
   // preferable but the table requires sorting on the Opcode name, but it's
   // static const initialized and matches the order of the spec.
   const size_t nameLength = strlen(name);
+  const auto version = spvVersionForTargetEnv(env);
   for (uint64_t opcodeIndex = 0; opcodeIndex < table->count; ++opcodeIndex) {
     const spv_opcode_desc_t& entry = table->entries[opcodeIndex];
     // We considers the current opcode as available as long as
@@ -107,7 +108,7 @@
     // Note that the second rule assumes the extension enabling this instruction
     // is indeed requested in the SPIR-V code; checking that should be
     // validator's work.
-    if ((spvVersionForTargetEnv(env) >= entry.minVersion ||
+    if (((version >= entry.minVersion && version <= entry.lastVersion) ||
          entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
         nameLength == strlen(entry.name) &&
         !strncmp(name, entry.name, nameLength)) {
@@ -130,8 +131,8 @@
   const auto beg = table->entries;
   const auto end = table->entries + table->count;
 
-  spv_opcode_desc_t needle = {"",    opcode, 0, nullptr, 0,  {},
-                              false, false,  0, nullptr, ~0u};
+  spv_opcode_desc_t needle = {"",    opcode, 0, nullptr, 0,   {},
+                              false, false,  0, nullptr, ~0u, ~0u};
 
   auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
     return lhs.opcode < rhs.opcode;
@@ -142,6 +143,7 @@
   // which means they can have different minimal version requirements.
   // Assumes the underlying table is already sorted ascendingly according to
   // opcode value.
+  const auto version = spvVersionForTargetEnv(env);
   for (auto it = std::lower_bound(beg, end, needle, comp);
        it != end && it->opcode == opcode; ++it) {
     // We considers the current opcode as available as long as
@@ -152,7 +154,7 @@
     // Note that the second rule assumes the extension enabling this instruction
     // is indeed requested in the SPIR-V code; checking that should be
     // validator's work.
-    if (spvVersionForTargetEnv(env) >= it->minVersion ||
+    if ((version >= it->minVersion && version <= it->lastVersion) ||
         it->numExtensions > 0u || it->numCapabilities > 0u) {
       *pEntry = it;
       return SPV_SUCCESS;
@@ -182,8 +184,8 @@
 const char* spvOpcodeString(const SpvOp opcode) {
   const auto beg = kOpcodeTableEntries;
   const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries);
-  spv_opcode_desc_t needle = {"",    opcode, 0, nullptr, 0,  {},
-                              false, false,  0, nullptr, ~0u};
+  spv_opcode_desc_t needle = {"",    opcode, 0, nullptr, 0,   {},
+                              false, false,  0, nullptr, ~0u, ~0u};
   auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
     return lhs.opcode < rhs.opcode;
   };
@@ -260,6 +262,7 @@
     case SpvOpTypeMatrix:
     case SpvOpTypeArray:
     case SpvOpTypeStruct:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       return false;
@@ -325,6 +328,7 @@
     case SpvOpTypePipeStorage:
     case SpvOpTypeNamedBarrier:
     case SpvOpTypeAccelerationStructureNV:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       // In particular, OpTypeForwardPointer does not generate a type,
@@ -603,3 +607,35 @@
       return false;
   }
 }
+
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpMemoryBarrier:
+      return {1};
+    case SpvOpAtomicStore:
+    case SpvOpControlBarrier:
+    case SpvOpAtomicFlagClear:
+    case SpvOpMemoryNamedBarrier:
+      return {2};
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+      return {4};
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+      return {4, 5};
+    default:
+      return {};
+  }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 76f9a0e..ed64f1b 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -133,4 +133,8 @@
 // Returns true if the given opcode is a debug instruction.
 bool spvOpcodeIsDebug(SpvOp opcode);
 
+// Returns a vector containing the indices of the memory semantics <id>
+// operands for |opcode|.
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_
diff --git a/source/operand.cpp b/source/operand.cpp
index 00dc53d..cd26a3d 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -16,6 +16,7 @@
 
 #include <assert.h>
 #include <string.h>
+
 #include <algorithm>
 
 #include "source/macro.h"
@@ -50,6 +51,7 @@
   if (!table) return SPV_ERROR_INVALID_TABLE;
   if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER;
 
+  const auto version = spvVersionForTargetEnv(env);
   for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) {
     const auto& group = table->types[typeIndex];
     if (type != group.type) continue;
@@ -64,7 +66,7 @@
       // 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 ((spvVersionForTargetEnv(env) >= entry.minVersion ||
+      if (((version >= entry.minVersion && version <= entry.lastVersion) ||
            entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
           nameLength == strlen(entry.name) &&
           !strncmp(entry.name, name, nameLength)) {
@@ -85,7 +87,7 @@
   if (!table) return SPV_ERROR_INVALID_TABLE;
   if (!pEntry) return SPV_ERROR_INVALID_POINTER;
 
-  spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u};
+  spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u, ~0u};
 
   auto comp = [](const spv_operand_desc_t& lhs, const spv_operand_desc_t& rhs) {
     return lhs.value < rhs.value;
@@ -108,6 +110,7 @@
     // requirements.
     // Assumes the underlying table is already sorted ascendingly according to
     // opcode value.
+    const auto version = spvVersionForTargetEnv(env);
     for (auto it = std::lower_bound(beg, end, needle, comp);
          it != end && it->value == value; ++it) {
       // We consider the current operand as available as long as
@@ -119,7 +122,7 @@
       // 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 (spvVersionForTargetEnv(env) >= it->minVersion ||
+      if ((version >= it->minVersion && version <= it->lastVersion) ||
           it->numExtensions > 0u || it->numCapabilities > 0u) {
         *pEntry = it;
         return SPV_SUCCESS;
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 0f3835c..3e1280e 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -15,13 +15,13 @@
   aggressive_dead_code_elim_pass.h
   basic_block.h
   block_merge_pass.h
+  block_merge_util.h
   build_module.h
   ccp_pass.h
   cfg_cleanup_pass.h
   cfg.h
   code_sink.h
   combine_access_chains.h
-  common_uniform_elim_pass.h
   compact_ids_pass.h
   composite.h
   const_folding_rules.h
@@ -30,19 +30,24 @@
   dead_branch_elim_pass.h
   dead_insert_elim_pass.h
   dead_variable_elimination.h
+  decompose_initialized_variables_pass.h
   decoration_manager.h
   def_use_manager.h
   dominator_analysis.h
   dominator_tree.h
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
+  eliminate_dead_functions_util.h
+  eliminate_dead_members_pass.h
   feature_manager.h
+  fix_storage_class.h
   flatten_decoration_pass.h
   fold.h
   folding_rules.h
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
   function.h
+  generate_webgpu_initializers_pass.h
   if_conversion.h
   inline_exhaustive_pass.h
   inline_opaque_pass.h
@@ -91,8 +96,10 @@
   scalar_replacement_pass.h
   set_spec_constant_default_value_pass.h
   simplification_pass.h
+  split_invalid_unreachable_pass.h
   ssa_rewrite_pass.h
   strength_reduction_pass.h
+  strip_atomic_counter_memory_pass.h
   strip_debug_info_pass.h
   strip_reflect_info_pass.h
   struct_cfg_analysis.h
@@ -108,13 +115,13 @@
   aggressive_dead_code_elim_pass.cpp
   basic_block.cpp
   block_merge_pass.cpp
+  block_merge_util.cpp
   build_module.cpp
   ccp_pass.cpp
   cfg_cleanup_pass.cpp
   cfg.cpp
   code_sink.cpp
   combine_access_chains.cpp
-  common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
   composite.cpp
   const_folding_rules.cpp
@@ -123,19 +130,24 @@
   dead_branch_elim_pass.cpp
   dead_insert_elim_pass.cpp
   dead_variable_elimination.cpp
+  decompose_initialized_variables_pass.cpp
   decoration_manager.cpp
   def_use_manager.cpp
   dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
+  eliminate_dead_functions_util.cpp
+  eliminate_dead_members_pass.cpp
   feature_manager.cpp
+  fix_storage_class.cpp
   flatten_decoration_pass.cpp
   fold.cpp
   folding_rules.cpp
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
   function.cpp
+  generate_webgpu_initializers_pass.cpp
   if_conversion.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
@@ -146,6 +158,7 @@
   instrument_pass.cpp
   ir_context.cpp
   ir_loader.cpp
+  legalize_vector_shuffle_pass.cpp
   licm_pass.cpp
   local_access_chain_convert_pass.cpp
   local_redundancy_elimination.cpp
@@ -181,8 +194,10 @@
   scalar_replacement_pass.cpp
   set_spec_constant_default_value_pass.cpp
   simplification_pass.cpp
+  split_invalid_unreachable_pass.cpp
   ssa_rewrite_pass.cpp
   strength_reduction_pass.cpp
+  strip_atomic_counter_memory_pass.cpp
   strip_debug_info_pass.cpp
   strip_reflect_info_pass.cpp
   struct_cfg_analysis.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 82d7499..11a9574 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -24,6 +24,7 @@
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/opt/iterator.h"
 #include "source/opt/reflect.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -489,6 +490,28 @@
         ProcessLoad(varId);
       }
     }
+
+    // Add OpDecorateId instructions that apply to this instruction to the work
+    // list.  We use the decoration manager to look through the group
+    // decorations to get to the OpDecorate* instructions themselves.
+    auto decorations =
+        get_decoration_mgr()->GetDecorationsFor(liveInst->result_id(), false);
+    for (Instruction* dec : decorations) {
+      // We only care about OpDecorateId instructions because the are the only
+      // decorations that will reference an id that will have to be kept live
+      // because of that use.
+      if (dec->opcode() != SpvOpDecorateId) {
+        continue;
+      }
+      if (dec->GetSingleWordInOperand(1) ==
+          SpvDecorationHlslCounterBufferGOOGLE) {
+        // These decorations should not force the use id to be live.  It will be
+        // removed if either the target or the in operand are dead.
+        continue;
+      }
+      AddToWorklist(dec);
+    }
+
     worklist_.pop();
   }
 
@@ -513,6 +536,26 @@
       AddBranch(mergeBlockId, *bi);
       for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
       }
+
+      auto merge_terminator = (*bi)->terminator();
+      if (merge_terminator->opcode() == SpvOpUnreachable) {
+        // The merge was unreachable. This is undefined behaviour so just
+        // return (or return an undef). Then mark the new return as live.
+        auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id());
+        if (func_ret_type_inst->opcode() == SpvOpTypeVoid) {
+          merge_terminator->SetOpcode(SpvOpReturn);
+        } else {
+          // Find an undef for the return value and make sure it gets kept by
+          // the pass.
+          auto undef_id = Type2Undef(func->type_id());
+          auto undef = get_def_use_mgr()->GetDef(undef_id);
+          live_insts_.Set(undef->unique_id());
+          merge_terminator->SetOpcode(SpvOpReturnValue);
+          merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}});
+          get_def_use_mgr()->AnalyzeInstUse(merge_terminator);
+        }
+        live_insts_.Set(merge_terminator->unique_id());
+      }
     } else {
       ++bi;
     }
@@ -528,15 +571,49 @@
   }
   // Keep all entry points.
   for (auto& entry : get_module()->entry_points()) {
-    AddToWorklist(&entry);
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+      // In SPIR-V 1.4 and later, entry points must list all global variables
+      // used. DCE can still remove non-input/output variables and update the
+      // interface list. Mark the entry point as live and inputs and outputs as
+      // live, but defer decisions all other interfaces.
+      live_insts_.Set(entry.unique_id());
+      // The actual function is live always.
+      AddToWorklist(
+          get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
+      for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
+        auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+        auto storage_class = var->GetSingleWordInOperand(0u);
+        if (storage_class == SpvStorageClassInput ||
+            storage_class == SpvStorageClassOutput) {
+          AddToWorklist(var);
+        }
+      }
+    } else {
+      AddToWorklist(&entry);
+    }
   }
-  // Keep workgroup size.
   for (auto& anno : get_module()->annotations()) {
     if (anno.opcode() == SpvOpDecorate) {
+      // Keep workgroup size.
       if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn &&
           anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) {
         AddToWorklist(&anno);
       }
+
+      if (context()->preserve_bindings()) {
+        // Keep all bindings.
+        if ((anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) ||
+            (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)) {
+          AddToWorklist(&anno);
+        }
+      }
+
+      if (context()->preserve_spec_constants()) {
+        // Keep all specialization constant instructions
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationSpecId) {
+          AddToWorklist(&anno);
+        }
+      }
     }
   }
 }
@@ -546,23 +623,23 @@
   // TODO(greg-lunarg): Handle additional capabilities
   if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
     return Status::SuccessWithoutChange;
+
   // Current functionality assumes relaxed logical addressing (see
   // instruction.h)
   // TODO(greg-lunarg): Handle non-logical addressing
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
+  // The variable pointer extension is no longer needed to use the capability,
+  // so we have to look for the capability.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer))
+    return Status::SuccessWithoutChange;
+
   // If any extensions in the module are not explicitly supported,
   // return unmodified.
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
 
-  // 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
-  // |OpGroupDecorate| instruction directly without informing the decoration
-  // manager.  This can put it in an invalid state which will cause an error
-  // when the context tries to update it.  To avoid this problem invalidate
-  // the decoration manager upfront.
-  context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
-
   // Eliminate Dead functions.
   bool modified = EliminateDeadFunctions();
 
@@ -572,6 +649,17 @@
   ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
   modified |= context()->ProcessEntryPointCallTree(pfn);
 
+  // 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
+  // |OpGroupDecorate| instruction directly without informing the decoration
+  // manager.  This can put it in an invalid state which will cause an error
+  // when the context tries to update it.  To avoid this problem invalidate
+  // the decoration manager upfront.
+  //
+  // We kill it at now because it is used when processing the entry point
+  // functions.
+  context()->InvalidateAnalyses(IRContext::Analysis::kAnalysisDecorations);
+
   // Process module-level instructions. Now that all live instructions have
   // been marked, it is safe to remove dead global values.
   modified |= ProcessGlobalValues();
@@ -752,6 +840,29 @@
     }
   }
 
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // Remove the dead interface variables from the entry point interface list.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        if (i < 3) {
+          // Execution model, function id and name are always valid.
+          new_operands.push_back(entry.GetInOperand(i));
+        } else {
+          auto* var =
+              get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+          if (!IsDead(var)) {
+            new_operands.push_back(entry.GetInOperand(i));
+          }
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        get_def_use_mgr()->UpdateDefUse(&entry);
+      }
+    }
+  }
+
   return modified;
 }
 
@@ -797,6 +908,7 @@
       "SPV_AMD_gpu_shader_half_float_fetch",
       "SPV_GOOGLE_decorate_string",
       "SPV_GOOGLE_hlsl_functionality1",
+      "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp
index aafee51..3608448 100644
--- a/source/opt/basic_block.cpp
+++ b/source/opt/basic_block.cpp
@@ -108,21 +108,29 @@
 
 void BasicBlock::ForEachSuccessorLabel(
     const std::function<void(const uint32_t)>& f) const {
+  WhileEachSuccessorLabel([f](const uint32_t l) {
+    f(l);
+    return true;
+  });
+}
+
+bool BasicBlock::WhileEachSuccessorLabel(
+    const std::function<bool(const uint32_t)>& f) const {
   const auto br = &insts_.back();
   switch (br->opcode()) {
-    case SpvOpBranch: {
-      f(br->GetOperand(0).words[0]);
-    } break;
+    case SpvOpBranch:
+      return f(br->GetOperand(0).words[0]);
     case SpvOpBranchConditional:
     case SpvOpSwitch: {
       bool is_first = true;
-      br->ForEachInId([&is_first, &f](const uint32_t* idp) {
-        if (!is_first) f(*idp);
+      return br->WhileEachInId([&is_first, &f](const uint32_t* idp) {
+        if (!is_first) return f(*idp);
         is_first = false;
+        return true;
       });
-    } break;
+    }
     default:
-      break;
+      return true;
   }
 }
 
@@ -184,6 +192,12 @@
   return mbid;
 }
 
+uint32_t BasicBlock::MergeBlockId() const {
+  uint32_t mbid = MergeBlockIdIfAny();
+  assert(mbid && "Expected block to have a corresponding merge block");
+  return mbid;
+}
+
 uint32_t BasicBlock::ContinueBlockIdIfAny() const {
   auto merge_ii = cend();
   --merge_ii;
@@ -197,6 +211,12 @@
   return cbid;
 }
 
+uint32_t BasicBlock::ContinueBlockId() const {
+  uint32_t cbid = ContinueBlockIdIfAny();
+  assert(cbid && "Expected block to have a corresponding continue target");
+  return cbid;
+}
+
 std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
   str << block.PrettyPrint();
   return str;
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index ff3a412..0bab337 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -160,6 +160,11 @@
   void ForEachSuccessorLabel(
       const std::function<void(const uint32_t)>& f) const;
 
+  // Runs the given function |f| on each label id of each successor block.  If
+  // |f| returns false, iteration is terminated and this function returns false.
+  bool WhileEachSuccessorLabel(
+      const std::function<bool(const uint32_t)>& f) const;
+
   // Runs the given function |f| on each label id of each successor block.
   // Modifying the pointed value will change the branch taken by the basic
   // block. It is the caller responsibility to update or invalidate the CFG.
@@ -183,10 +188,16 @@
   // block, if any.  If none, returns zero.
   uint32_t MergeBlockIdIfAny() const;
 
+  // Returns MergeBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t MergeBlockId() const;
+
   // Returns the ID of the continue block declared by a merge instruction in
   // this block, if any.  If none, returns zero.
   uint32_t ContinueBlockIdIfAny() const;
 
+  // Returns ContinueBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t ContinueBlockId() const;
+
   // Returns the terminator instruction.  Assumes the terminator exists.
   Instruction* terminator() { return &*tail(); }
   const Instruction* terminator() const { return &*ctail(); }
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index 09deb21..c7315ba 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "source/opt/block_merge_util.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/iterator.h"
 
@@ -27,112 +28,17 @@
 bool BlockMergePass::MergeBlocks(Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end();) {
-    // Find block with single successor which has no other predecessors.
-    auto ii = bi->end();
-    --ii;
-    Instruction* br = &*ii;
-    if (br->opcode() != SpvOpBranch) {
+    if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+      blockmergeutil::MergeWithSuccessor(context(), func, bi);
+      // Reprocess block.
+      modified = true;
+    } else {
       ++bi;
-      continue;
     }
-
-    const uint32_t lab_id = br->GetSingleWordInOperand(0);
-    if (cfg()->preds(lab_id).size() != 1) {
-      ++bi;
-      continue;
-    }
-
-    bool pred_is_merge = IsMerge(&*bi);
-    bool succ_is_merge = IsMerge(lab_id);
-    if (pred_is_merge && succ_is_merge) {
-      // Cannot merge two merges together.
-      ++bi;
-      continue;
-    }
-
-    Instruction* merge_inst = bi->GetMergeInst();
-    bool pred_is_header = IsHeader(&*bi);
-    if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
-      bool succ_is_header = IsHeader(lab_id);
-      if (pred_is_header && succ_is_header) {
-        // Cannot merge two headers together when the successor is not the merge
-        // block of the predecessor.
-        ++bi;
-        continue;
-      }
-
-      // If this is a header block and the successor is not its merge, we must
-      // be careful about which blocks we are willing to merge together.
-      // OpLoopMerge must be followed by a conditional or unconditional branch.
-      // The merge must be a loop merge because a selection merge cannot be
-      // followed by an unconditional branch.
-      BasicBlock* succ_block = context()->get_instr_block(lab_id);
-      SpvOp succ_term_op = succ_block->terminator()->opcode();
-      assert(merge_inst->opcode() == SpvOpLoopMerge);
-      if (succ_term_op != SpvOpBranch &&
-          succ_term_op != SpvOpBranchConditional) {
-        ++bi;
-        continue;
-      }
-    }
-
-    // Merge blocks.
-    context()->KillInst(br);
-    auto sbi = bi;
-    for (; sbi != func->end(); ++sbi)
-      if (sbi->id() == lab_id) break;
-    // If bi is sbi's only predecessor, it dominates sbi and thus
-    // sbi must follow bi in func's ordering.
-    assert(sbi != func->end());
-
-    // Update the inst-to-block mapping for the instructions in sbi.
-    for (auto& inst : *sbi) {
-      context()->set_instr_block(&inst, &*bi);
-    }
-
-    // Now actually move the instructions.
-    bi->AddInstructions(&*sbi);
-
-    if (merge_inst) {
-      if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
-        // Merging the header and merge blocks, so remove the structured control
-        // flow declaration.
-        context()->KillInst(merge_inst);
-      } else {
-        // Move the merge instruction to just before the terminator.
-        merge_inst->InsertBefore(bi->terminator());
-      }
-    }
-    context()->ReplaceAllUsesWith(lab_id, bi->id());
-    context()->KillInst(sbi->GetLabelInst());
-    (void)sbi.Erase();
-    // Reprocess block.
-    modified = true;
   }
   return modified;
 }
 
-bool BlockMergePass::IsHeader(BasicBlock* block) {
-  return block->GetMergeInst() != nullptr;
-}
-
-bool BlockMergePass::IsHeader(uint32_t id) {
-  return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id)));
-}
-
-bool BlockMergePass::IsMerge(uint32_t id) {
-  return !get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
-                                                 uint32_t index) {
-    SpvOp op = user->opcode();
-    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
-      return false;
-    }
-    return true;
-  });
-}
-
-bool BlockMergePass::IsMerge(BasicBlock* block) { return IsMerge(block->id()); }
-
 Pass::Status BlockMergePass::Process() {
   // Process all entry point functions.
   ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
diff --git a/source/opt/block_merge_pass.h b/source/opt/block_merge_pass.h
index 3ae7a5c..aabf789 100644
--- a/source/opt/block_merge_pass.h
+++ b/source/opt/block_merge_pass.h
@@ -54,14 +54,6 @@
   // with no other predecessors. Merge these blocks into a single block.
   bool MergeBlocks(Function* func);
 
-  // Returns true if |block| (or |id|) contains a merge instruction.
-  bool IsHeader(BasicBlock* block);
-  bool IsHeader(uint32_t id);
-
-  // Returns true if |block| (or |id|) is the merge target of a merge
-  // instruction.
-  bool IsMerge(BasicBlock* block);
-  bool IsMerge(uint32_t id);
 };
 
 }  // namespace opt
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
new file mode 100644
index 0000000..107723d
--- /dev/null
+++ b/source/opt/block_merge_util.cpp
@@ -0,0 +1,168 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "block_merge_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace blockmergeutil {
+
+namespace {
+
+// Returns true if |block| contains a merge instruction.
+bool IsHeader(BasicBlock* block) { return block->GetMergeInst() != nullptr; }
+
+// Returns true if |id| contains a merge instruction.
+bool IsHeader(IRContext* context, uint32_t id) {
+  return IsHeader(
+      context->get_instr_block(context->get_def_use_mgr()->GetDef(id)));
+}
+
+// Returns true if |id| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, uint32_t id) {
+  return !context->get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
+                                                          uint32_t index) {
+    SpvOp op = user->opcode();
+    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
+      return false;
+    }
+    return true;
+  });
+}
+
+// Returns true if |block| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, BasicBlock* block) {
+  return IsMerge(context, block->id());
+}
+
+// Removes any OpPhi instructions in |block|, which should have exactly one
+// predecessor, replacing uses of OpPhi ids with the ids associated with the
+// predecessor.
+void EliminateOpPhiInstructions(IRContext* context, BasicBlock* block) {
+  block->ForEachPhiInst([context](Instruction* phi) {
+    assert(2 == phi->NumInOperands() &&
+           "A block can only have one predecessor for block merging to make "
+           "sense.");
+    context->ReplaceAllUsesWith(phi->result_id(),
+                                phi->GetSingleWordInOperand(0));
+    context->KillInst(phi);
+  });
+}
+
+}  // Anonymous namespace
+
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
+  // Find block with single successor which has no other predecessors.
+  auto ii = block->end();
+  --ii;
+  Instruction* br = &*ii;
+  if (br->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  if (context->cfg()->preds(lab_id).size() != 1) {
+    return false;
+  }
+
+  bool pred_is_merge = IsMerge(context, block);
+  bool succ_is_merge = IsMerge(context, lab_id);
+  if (pred_is_merge && succ_is_merge) {
+    // Cannot merge two merges together.
+    return false;
+  }
+
+  // Don't bother trying to merge unreachable blocks.
+  if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
+    if (!dominators->IsReachable(block)) return false;
+  }
+
+  Instruction* merge_inst = block->GetMergeInst();
+  const bool pred_is_header = IsHeader(block);
+  if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
+    bool succ_is_header = IsHeader(context, lab_id);
+    if (pred_is_header && succ_is_header) {
+      // Cannot merge two headers together when the successor is not the merge
+      // block of the predecessor.
+      return false;
+    }
+
+    // If this is a header block and the successor is not its merge, we must
+    // be careful about which blocks we are willing to merge together.
+    // OpLoopMerge must be followed by a conditional or unconditional branch.
+    // The merge must be a loop merge because a selection merge cannot be
+    // followed by an unconditional branch.
+    BasicBlock* succ_block = context->get_instr_block(lab_id);
+    SpvOp succ_term_op = succ_block->terminator()->opcode();
+    assert(merge_inst->opcode() == SpvOpLoopMerge);
+    if (succ_term_op != SpvOpBranch && succ_term_op != SpvOpBranchConditional) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi) {
+  assert(CanMergeWithSuccessor(context, &*bi) &&
+         "Precondition failure for MergeWithSuccessor: it must be legal to "
+         "merge the block and its successor.");
+
+  auto ii = bi->end();
+  --ii;
+  Instruction* br = &*ii;
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  Instruction* merge_inst = bi->GetMergeInst();
+  bool pred_is_header = IsHeader(&*bi);
+
+  // Merge blocks.
+  context->KillInst(br);
+  auto sbi = bi;
+  for (; sbi != func->end(); ++sbi)
+    if (sbi->id() == lab_id) break;
+  // If bi is sbi's only predecessor, it dominates sbi and thus
+  // sbi must follow bi in func's ordering.
+  assert(sbi != func->end());
+
+  // Update the inst-to-block mapping for the instructions in sbi.
+  for (auto& inst : *sbi) {
+    context->set_instr_block(&inst, &*bi);
+  }
+
+  EliminateOpPhiInstructions(context, &*sbi);
+
+  // Now actually move the instructions.
+  bi->AddInstructions(&*sbi);
+
+  if (merge_inst) {
+    if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
+      // Merging the header and merge blocks, so remove the structured control
+      // flow declaration.
+      context->KillInst(merge_inst);
+    } else {
+      // Move the merge instruction to just before the terminator.
+      merge_inst->InsertBefore(bi->terminator());
+    }
+  }
+  context->ReplaceAllUsesWith(lab_id, bi->id());
+  context->KillInst(sbi->GetLabelInst());
+  (void)sbi.Erase();
+}
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/block_merge_util.h b/source/opt/block_merge_util.h
new file mode 100644
index 0000000..e71e3d6
--- /dev/null
+++ b/source/opt/block_merge_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+#define SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for determining when it is safe to merge blocks, and for
+// actually merging blocks, for use by various analyses and passes.
+namespace blockmergeutil {
+
+// Returns true if and only if |block| has exactly one successor and merging
+// this successor into |block| has no impact on the semantics or validity of the
+// SPIR-V module.
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block);
+
+// Requires that |bi| has a successor that can be safely merged into |bi|, and
+// performs the merge.
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi);
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_BLOCK_MERGE_UTIL_H_
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8356195..2de9250 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -271,6 +271,7 @@
     uint32_t id = it.first;
     uint32_t cst_id = it.second;
     if (!IsVaryingValue(cst_id) && id != cst_id) {
+      context()->KillNamesAndDecorates(id);
       retval |= context()->ReplaceAllUsesWith(id, cst_id);
     }
   }
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 7e1097e..72693cb 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -150,15 +150,25 @@
 void CFG::ComputePostOrderTraversal(BasicBlock* bb,
                                     std::vector<BasicBlock*>* order,
                                     std::unordered_set<BasicBlock*>* seen) {
-  seen->insert(bb);
-  static_cast<const BasicBlock*>(bb)->ForEachSuccessorLabel(
-      [&order, &seen, this](const uint32_t sbid) {
-        BasicBlock* succ_bb = id2block_[sbid];
-        if (!seen->count(succ_bb)) {
-          ComputePostOrderTraversal(succ_bb, order, seen);
-        }
-      });
-  order->push_back(bb);
+  std::vector<BasicBlock*> stack;
+  stack.push_back(bb);
+  while (!stack.empty()) {
+    bb = stack.back();
+    seen->insert(bb);
+    static_cast<const BasicBlock*>(bb)->WhileEachSuccessorLabel(
+        [&seen, &stack, this](const uint32_t sbid) {
+          BasicBlock* succ_bb = id2block_[sbid];
+          if (!seen->count(succ_bb)) {
+            stack.push_back(succ_bb);
+            return false;
+          }
+          return true;
+        });
+    if (stack.back() == bb) {
+      order->push_back(bb);
+      stack.pop_back();
+    }
+  }
 }
 
 BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
index e1e8190..9d54ee5 100644
--- a/source/opt/code_sink.cpp
+++ b/source/opt/code_sink.cpp
@@ -80,7 +80,10 @@
   get_def_use_mgr()->ForEachUse(
       inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
         if (use->opcode() != SpvOpPhi) {
-          bbs_with_uses.insert(context()->get_instr_block(use)->id());
+          BasicBlock* use_bb = context()->get_instr_block(use);
+          if (use_bb) {
+            bbs_with_uses.insert(use_bb->id());
+          }
         } else {
           bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
         }
diff --git a/source/opt/common_uniform_elim_pass.cpp b/source/opt/common_uniform_elim_pass.cpp
deleted file mode 100644
index 52d279f..0000000
--- a/source/opt/common_uniform_elim_pass.cpp
+++ /dev/null
@@ -1,592 +0,0 @@
-// Copyright (c) 2017 The Khronos Group Inc.
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 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/common_uniform_elim_pass.h"
-#include "source/cfa.h"
-#include "source/opt/ir_context.h"
-
-namespace spvtools {
-namespace opt {
-
-namespace {
-
-const uint32_t kAccessChainPtrIdInIdx = 0;
-const uint32_t kTypePointerStorageClassInIdx = 0;
-const uint32_t kTypePointerTypeIdInIdx = 1;
-const uint32_t kConstantValueInIdx = 0;
-const uint32_t kExtractCompositeIdInIdx = 0;
-const uint32_t kExtractIdx0InIdx = 1;
-const uint32_t kStorePtrIdInIdx = 0;
-const uint32_t kLoadPtrIdInIdx = 0;
-const uint32_t kCopyObjectOperandInIdx = 0;
-const uint32_t kTypeIntWidthInIdx = 0;
-
-}  // anonymous namespace
-
-bool CommonUniformElimPass::IsNonPtrAccessChain(const SpvOp opcode) const {
-  return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain;
-}
-
-bool CommonUniformElimPass::IsSamplerOrImageType(
-    const Instruction* typeInst) const {
-  switch (typeInst->opcode()) {
-    case SpvOpTypeSampler:
-    case SpvOpTypeImage:
-    case SpvOpTypeSampledImage:
-      return true;
-    default:
-      break;
-  }
-  if (typeInst->opcode() != SpvOpTypeStruct) return false;
-  // Return true if any member is a sampler or image
-  return !typeInst->WhileEachInId([this](const uint32_t* tid) {
-    const Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid);
-    if (IsSamplerOrImageType(compTypeInst)) {
-      return false;
-    }
-    return true;
-  });
-}
-
-bool CommonUniformElimPass::IsSamplerOrImageVar(uint32_t varId) const {
-  const Instruction* varInst = get_def_use_mgr()->GetDef(varId);
-  assert(varInst->opcode() == SpvOpVariable);
-  const uint32_t varTypeId = varInst->type_id();
-  const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
-  const uint32_t varPteTypeId =
-      varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
-  Instruction* varPteTypeInst = get_def_use_mgr()->GetDef(varPteTypeId);
-  return IsSamplerOrImageType(varPteTypeInst);
-}
-
-Instruction* CommonUniformElimPass::GetPtr(Instruction* ip, uint32_t* objId) {
-  const SpvOp op = ip->opcode();
-  assert(op == SpvOpStore || op == SpvOpLoad);
-  *objId = ip->GetSingleWordInOperand(op == SpvOpStore ? kStorePtrIdInIdx
-                                                       : kLoadPtrIdInIdx);
-  Instruction* ptrInst = get_def_use_mgr()->GetDef(*objId);
-  while (ptrInst->opcode() == SpvOpCopyObject) {
-    *objId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
-    ptrInst = get_def_use_mgr()->GetDef(*objId);
-  }
-  Instruction* objInst = ptrInst;
-  while (objInst->opcode() != SpvOpVariable &&
-         objInst->opcode() != SpvOpFunctionParameter) {
-    if (IsNonPtrAccessChain(objInst->opcode())) {
-      *objId = objInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
-    } else {
-      assert(objInst->opcode() == SpvOpCopyObject);
-      *objId = objInst->GetSingleWordInOperand(kCopyObjectOperandInIdx);
-    }
-    objInst = get_def_use_mgr()->GetDef(*objId);
-  }
-  return ptrInst;
-}
-
-bool CommonUniformElimPass::IsVolatileStruct(uint32_t type_id) {
-  assert(get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct);
-  return !get_decoration_mgr()->WhileEachDecoration(
-      type_id, SpvDecorationVolatile, [](const Instruction&) { return false; });
-}
-
-bool CommonUniformElimPass::IsAccessChainToVolatileStructType(
-    const Instruction& AccessChainInst) {
-  assert(AccessChainInst.opcode() == SpvOpAccessChain);
-
-  uint32_t ptr_id = AccessChainInst.GetSingleWordInOperand(0);
-  const Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
-  const uint32_t num_operands = AccessChainInst.NumOperands();
-
-  // walk the type tree:
-  for (uint32_t idx = 3; idx < num_operands; ++idx) {
-    Instruction* pointee_type = get_def_use_mgr()->GetDef(pointee_type_id);
-
-    switch (pointee_type->opcode()) {
-      case SpvOpTypeMatrix:
-      case SpvOpTypeVector:
-      case SpvOpTypeArray:
-      case SpvOpTypeRuntimeArray:
-        pointee_type_id = pointee_type->GetSingleWordOperand(1);
-        break;
-      case SpvOpTypeStruct:
-        // check for volatile decorations:
-        if (IsVolatileStruct(pointee_type_id)) return true;
-
-        if (idx < num_operands - 1) {
-          const uint32_t index_id = AccessChainInst.GetSingleWordOperand(idx);
-          const Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
-          uint32_t index_value = index_inst->GetSingleWordOperand(
-              2);  // TODO: replace with GetUintValueFromConstant()
-          pointee_type_id = pointee_type->GetSingleWordInOperand(index_value);
-        }
-        break;
-      default:
-        assert(false && "Unhandled pointee type.");
-    }
-  }
-  return false;
-}
-
-bool CommonUniformElimPass::IsVolatileLoad(const Instruction& loadInst) {
-  assert(loadInst.opcode() == SpvOpLoad);
-  // Check if this Load instruction has Volatile Memory Access flag
-  if (loadInst.NumOperands() == 4) {
-    uint32_t memory_access_mask = loadInst.GetSingleWordOperand(3);
-    if (memory_access_mask & SpvMemoryAccessVolatileMask) return true;
-  }
-  // If we load a struct directly (result type is struct),
-  // check if the struct is decorated volatile
-  uint32_t type_id = loadInst.type_id();
-  if (get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct)
-    return IsVolatileStruct(type_id);
-  else
-    return false;
-}
-
-bool CommonUniformElimPass::IsUniformVar(uint32_t varId) {
-  const Instruction* varInst =
-      get_def_use_mgr()->id_to_defs().find(varId)->second;
-  if (varInst->opcode() != SpvOpVariable) return false;
-  const uint32_t varTypeId = varInst->type_id();
-  const Instruction* varTypeInst =
-      get_def_use_mgr()->id_to_defs().find(varTypeId)->second;
-  return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
-             SpvStorageClassUniform ||
-         varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) ==
-             SpvStorageClassUniformConstant;
-}
-
-bool CommonUniformElimPass::HasUnsupportedDecorates(uint32_t id) const {
-  return !get_def_use_mgr()->WhileEachUser(id, [this](Instruction* user) {
-    if (IsNonTypeDecorate(user->opcode())) return false;
-    return true;
-  });
-}
-
-bool CommonUniformElimPass::HasOnlyNamesAndDecorates(uint32_t id) const {
-  return get_def_use_mgr()->WhileEachUser(id, [this](Instruction* user) {
-    SpvOp op = user->opcode();
-    if (op != SpvOpName && !IsNonTypeDecorate(op)) return false;
-    return true;
-  });
-}
-
-void CommonUniformElimPass::DeleteIfUseless(Instruction* inst) {
-  const uint32_t resId = inst->result_id();
-  assert(resId != 0);
-  if (HasOnlyNamesAndDecorates(resId)) {
-    context()->KillInst(inst);
-  }
-}
-
-Instruction* CommonUniformElimPass::ReplaceAndDeleteLoad(Instruction* loadInst,
-                                                         uint32_t replId,
-                                                         Instruction* ptrInst) {
-  const uint32_t loadId = loadInst->result_id();
-  context()->KillNamesAndDecorates(loadId);
-  (void)context()->ReplaceAllUsesWith(loadId, replId);
-  // remove load instruction
-  Instruction* next_instruction = context()->KillInst(loadInst);
-  // if access chain, see if it can be removed as well
-  if (IsNonPtrAccessChain(ptrInst->opcode())) DeleteIfUseless(ptrInst);
-  return next_instruction;
-}
-
-void CommonUniformElimPass::GenACLoadRepl(
-    const Instruction* ptrInst,
-    std::vector<std::unique_ptr<Instruction>>* newInsts, uint32_t* resultId) {
-  // Build and append Load
-  const uint32_t ldResultId = TakeNextId();
-  const uint32_t varId =
-      ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx);
-  const Instruction* varInst = get_def_use_mgr()->GetDef(varId);
-  assert(varInst->opcode() == SpvOpVariable);
-  const uint32_t varPteTypeId = GetPointeeTypeId(varInst);
-  std::vector<Operand> load_in_operands;
-  load_in_operands.push_back(Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-                                     std::initializer_list<uint32_t>{varId}));
-  std::unique_ptr<Instruction> newLoad(new Instruction(
-      context(), SpvOpLoad, varPteTypeId, ldResultId, load_in_operands));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad);
-  newInsts->emplace_back(std::move(newLoad));
-
-  // Build and append Extract
-  const uint32_t extResultId = TakeNextId();
-  const uint32_t ptrPteTypeId = GetPointeeTypeId(ptrInst);
-  std::vector<Operand> ext_in_opnds;
-  ext_in_opnds.push_back(Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-                                 std::initializer_list<uint32_t>{ldResultId}));
-  uint32_t iidIdx = 0;
-  ptrInst->ForEachInId([&iidIdx, &ext_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);
-      ext_in_opnds.push_back(
-          Operand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
-                  std::initializer_list<uint32_t>{val}));
-    }
-    ++iidIdx;
-  });
-  std::unique_ptr<Instruction> newExt(
-      new Instruction(context(), SpvOpCompositeExtract, ptrPteTypeId,
-                      extResultId, ext_in_opnds));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*newExt);
-  newInsts->emplace_back(std::move(newExt));
-  *resultId = extResultId;
-}
-
-bool CommonUniformElimPass::IsConstantIndexAccessChain(Instruction* acp) {
-  uint32_t inIdx = 0;
-  return acp->WhileEachInId([&inIdx, this](uint32_t* tid) {
-    if (inIdx > 0) {
-      Instruction* opInst = get_def_use_mgr()->GetDef(*tid);
-      if (opInst->opcode() != SpvOpConstant) return false;
-    }
-    ++inIdx;
-    return true;
-  });
-}
-
-bool CommonUniformElimPass::UniformAccessChainConvert(Function* func) {
-  bool modified = false;
-  for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    for (Instruction* inst = &*bi->begin(); inst; inst = inst->NextNode()) {
-      if (inst->opcode() != SpvOpLoad) continue;
-      uint32_t varId;
-      Instruction* ptrInst = GetPtr(inst, &varId);
-      if (!IsNonPtrAccessChain(ptrInst->opcode())) continue;
-      // Do not convert nested access chains
-      if (ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx) != varId)
-        continue;
-      if (!IsUniformVar(varId)) continue;
-      if (!IsConstantIndexAccessChain(ptrInst)) continue;
-      if (HasUnsupportedDecorates(inst->result_id())) continue;
-      if (HasUnsupportedDecorates(ptrInst->result_id())) continue;
-      if (IsVolatileLoad(*inst)) continue;
-      if (IsAccessChainToVolatileStructType(*ptrInst)) continue;
-      std::vector<std::unique_ptr<Instruction>> newInsts;
-      uint32_t replId;
-      GenACLoadRepl(ptrInst, &newInsts, &replId);
-      inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
-      assert(inst->opcode() != SpvOpPhi);
-      inst = inst->InsertBefore(std::move(newInsts));
-      modified = true;
-    }
-  }
-  return modified;
-}
-
-void CommonUniformElimPass::ComputeStructuredSuccessors(Function* func) {
-  block2structured_succs_.clear();
-  for (auto& blk : *func) {
-    // If header, make merge block first successor.
-    uint32_t mbid = blk.MergeBlockIdIfAny();
-    if (mbid != 0) {
-      block2structured_succs_[&blk].push_back(cfg()->block(mbid));
-      uint32_t cbid = blk.ContinueBlockIdIfAny();
-      if (cbid != 0) {
-        block2structured_succs_[&blk].push_back(cfg()->block(mbid));
-      }
-    }
-    // add true successors
-    const auto& const_blk = blk;
-    const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) {
-      block2structured_succs_[&blk].push_back(cfg()->block(sbid));
-    });
-  }
-}
-
-void CommonUniformElimPass::ComputeStructuredOrder(
-    Function* func, std::list<BasicBlock*>* order) {
-  // Compute structured successors and do DFS
-  ComputeStructuredSuccessors(func);
-  auto ignore_block = [](cbb_ptr) {};
-  auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
-  auto get_structured_successors = [this](const BasicBlock* block) {
-    return &(block2structured_succs_[block]);
-  };
-  // TODO(greg-lunarg): Get rid of const_cast by making moving const
-  // out of the cfa.h prototypes and into the invoking code.
-  auto post_order = [&](cbb_ptr b) {
-    order->push_front(const_cast<BasicBlock*>(b));
-  };
-
-  order->clear();
-  CFA<BasicBlock>::DepthFirstTraversal(&*func->begin(),
-                                       get_structured_successors, ignore_block,
-                                       post_order, ignore_edge);
-}
-
-bool CommonUniformElimPass::CommonUniformLoadElimination(Function* func) {
-  // Process all blocks in structured order. This is just one way (the
-  // simplest?) to keep track of the most recent block outside of control
-  // flow, used to copy common instructions, guaranteed to dominate all
-  // following load sites.
-  std::list<BasicBlock*> structuredOrder;
-  ComputeStructuredOrder(func, &structuredOrder);
-  uniform2load_id_.clear();
-  bool modified = false;
-  // Find insertion point in first block to copy non-dominating loads.
-  auto insertItr = func->begin()->begin();
-  while (insertItr->opcode() == SpvOpVariable ||
-         insertItr->opcode() == SpvOpNop)
-    ++insertItr;
-  // Update insertItr until it will not be removed. Without this code,
-  // ReplaceAndDeleteLoad() can set |insertItr| as a dangling pointer.
-  while (IsUniformLoadToBeRemoved(&*insertItr)) ++insertItr;
-  uint32_t mergeBlockId = 0;
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    BasicBlock* bp = *bi;
-    // Check if we are exiting outermost control construct. If so, remember
-    // new load insertion point. Trying to keep register pressure down.
-    if (mergeBlockId == bp->id()) {
-      mergeBlockId = 0;
-      insertItr = bp->begin();
-      while (insertItr->opcode() == SpvOpPhi) {
-        ++insertItr;
-      }
-
-      // Update insertItr until it will not be removed. Without this code,
-      // ReplaceAndDeleteLoad() can set |insertItr| as a dangling pointer.
-      while (IsUniformLoadToBeRemoved(&*insertItr)) ++insertItr;
-    }
-    for (Instruction* inst = &*bp->begin(); inst; inst = inst->NextNode()) {
-      if (inst->opcode() != SpvOpLoad) continue;
-      uint32_t varId;
-      Instruction* ptrInst = GetPtr(inst, &varId);
-      if (ptrInst->opcode() != SpvOpVariable) continue;
-      if (!IsUniformVar(varId)) continue;
-      if (IsSamplerOrImageVar(varId)) continue;
-      if (HasUnsupportedDecorates(inst->result_id())) continue;
-      if (IsVolatileLoad(*inst)) continue;
-      uint32_t replId;
-      const auto uItr = uniform2load_id_.find(varId);
-      if (uItr != uniform2load_id_.end()) {
-        replId = uItr->second;
-      } else {
-        if (mergeBlockId == 0) {
-          // Load is in dominating block; just remember it
-          uniform2load_id_[varId] = inst->result_id();
-          continue;
-        } else {
-          // Copy load into most recent dominating block and remember it
-          replId = TakeNextId();
-          std::unique_ptr<Instruction> newLoad(new Instruction(
-              context(), SpvOpLoad, inst->type_id(), replId,
-              {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {varId}}}));
-          get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad);
-          insertItr = insertItr.InsertBefore(std::move(newLoad));
-          ++insertItr;
-          uniform2load_id_[varId] = replId;
-        }
-      }
-      inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
-      modified = true;
-    }
-    // If we are outside of any control construct and entering one, remember
-    // the id of the merge block
-    if (mergeBlockId == 0) {
-      mergeBlockId = bp->MergeBlockIdIfAny();
-    }
-  }
-  return modified;
-}
-
-bool CommonUniformElimPass::CommonUniformLoadElimBlock(Function* func) {
-  bool modified = false;
-  for (auto& blk : *func) {
-    uniform2load_id_.clear();
-    for (Instruction* inst = &*blk.begin(); inst; inst = inst->NextNode()) {
-      if (inst->opcode() != SpvOpLoad) continue;
-      uint32_t varId;
-      Instruction* ptrInst = GetPtr(inst, &varId);
-      if (ptrInst->opcode() != SpvOpVariable) continue;
-      if (!IsUniformVar(varId)) continue;
-      if (!IsSamplerOrImageVar(varId)) continue;
-      if (HasUnsupportedDecorates(inst->result_id())) continue;
-      if (IsVolatileLoad(*inst)) continue;
-      uint32_t replId;
-      const auto uItr = uniform2load_id_.find(varId);
-      if (uItr != uniform2load_id_.end()) {
-        replId = uItr->second;
-      } else {
-        uniform2load_id_[varId] = inst->result_id();
-        continue;
-      }
-      inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
-      modified = true;
-    }
-  }
-  return modified;
-}
-
-bool CommonUniformElimPass::CommonExtractElimination(Function* func) {
-  // Find all composite ids with duplicate extracts.
-  for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
-      if (ii->opcode() != SpvOpCompositeExtract) continue;
-      // TODO(greg-lunarg): Support multiple indices
-      if (ii->NumInOperands() > 2) continue;
-      if (HasUnsupportedDecorates(ii->result_id())) continue;
-      uint32_t compId = ii->GetSingleWordInOperand(kExtractCompositeIdInIdx);
-      uint32_t idx = ii->GetSingleWordInOperand(kExtractIdx0InIdx);
-      comp2idx2inst_[compId][idx].push_back(&*ii);
-    }
-  }
-  // For all defs of ids with duplicate extracts, insert new extracts
-  // after def, and replace and delete old extracts
-  bool modified = false;
-  for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
-      const auto cItr = comp2idx2inst_.find(ii->result_id());
-      if (cItr == comp2idx2inst_.end()) continue;
-      for (auto idxItr : cItr->second) {
-        if (idxItr.second.size() < 2) continue;
-        uint32_t replId = TakeNextId();
-        std::unique_ptr<Instruction> newExtract(
-            idxItr.second.front()->Clone(context()));
-        newExtract->SetResultId(replId);
-        get_def_use_mgr()->AnalyzeInstDefUse(&*newExtract);
-        ++ii;
-        ii = ii.InsertBefore(std::move(newExtract));
-        for (auto instItr : idxItr.second) {
-          uint32_t resId = instItr->result_id();
-          context()->KillNamesAndDecorates(resId);
-          (void)context()->ReplaceAllUsesWith(resId, replId);
-          context()->KillInst(instItr);
-        }
-        modified = true;
-      }
-    }
-  }
-  return modified;
-}
-
-bool CommonUniformElimPass::EliminateCommonUniform(Function* func) {
-  bool modified = false;
-  modified |= UniformAccessChainConvert(func);
-  modified |= CommonUniformLoadElimination(func);
-  modified |= CommonExtractElimination(func);
-
-  modified |= CommonUniformLoadElimBlock(func);
-  return modified;
-}
-
-void CommonUniformElimPass::Initialize() {
-  // Clear collections.
-  comp2idx2inst_.clear();
-
-  // Initialize extension whitelist
-  InitExtensions();
-}
-
-bool CommonUniformElimPass::AllExtensionsSupported() const {
-  // If any extension not in whitelist, return false
-  for (auto& ei : get_module()->extensions()) {
-    const char* extName =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
-    if (extensions_whitelist_.find(extName) == extensions_whitelist_.end())
-      return false;
-  }
-  return true;
-}
-
-Pass::Status CommonUniformElimPass::ProcessImpl() {
-  // Assumes all control flow structured.
-  // TODO(greg-lunarg): Do SSA rewrite for non-structured control flow
-  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
-    return Status::SuccessWithoutChange;
-  // Assumes logical addressing only
-  // TODO(greg-lunarg): Add support for physical addressing
-  if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
-    return Status::SuccessWithoutChange;
-  // Do not process if any disallowed extensions are enabled
-  if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
-  // Do not process if module contains OpGroupDecorate. Additional
-  // support required in KillNamesAndDecorates().
-  // TODO(greg-lunarg): Add support for OpGroupDecorate
-  for (auto& ai : get_module()->annotations())
-    if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange;
-  // 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;
-  // Process entry point functions
-  ProcessFunction pfn = [this](Function* fp) {
-    return EliminateCommonUniform(fp);
-  };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
-  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
-}
-
-CommonUniformElimPass::CommonUniformElimPass() = default;
-
-Pass::Status CommonUniformElimPass::Process() {
-  Initialize();
-  return ProcessImpl();
-}
-
-void CommonUniformElimPass::InitExtensions() {
-  extensions_whitelist_.clear();
-  extensions_whitelist_.insert({
-      "SPV_AMD_shader_explicit_vertex_parameter",
-      "SPV_AMD_shader_trinary_minmax",
-      "SPV_AMD_gcn_shader",
-      "SPV_KHR_shader_ballot",
-      "SPV_AMD_shader_ballot",
-      "SPV_AMD_gpu_shader_half_float",
-      "SPV_KHR_shader_draw_parameters",
-      "SPV_KHR_subgroup_vote",
-      "SPV_KHR_16bit_storage",
-      "SPV_KHR_device_group",
-      "SPV_KHR_multiview",
-      "SPV_NVX_multiview_per_view_attributes",
-      "SPV_NV_viewport_array2",
-      "SPV_NV_stereo_view_rendering",
-      "SPV_NV_sample_mask_override_coverage",
-      "SPV_NV_geometry_shader_passthrough",
-      "SPV_AMD_texture_gather_bias_lod",
-      "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
-      "SPV_AMD_gpu_shader_int16",
-      "SPV_KHR_post_depth_coverage",
-      "SPV_KHR_shader_atomic_counter_ops",
-      "SPV_EXT_shader_stencil_export",
-      "SPV_EXT_shader_viewport_index_layer",
-      "SPV_AMD_shader_image_load_store_lod",
-      "SPV_AMD_shader_fragment_mask",
-      "SPV_EXT_fragment_fully_covered",
-      "SPV_AMD_gpu_shader_half_float_fetch",
-      "SPV_GOOGLE_decorate_string",
-      "SPV_GOOGLE_hlsl_functionality1",
-      "SPV_NV_shader_subgroup_partitioned",
-      "SPV_EXT_descriptor_indexing",
-      "SPV_NV_fragment_shader_barycentric",
-      "SPV_NV_compute_shader_derivatives",
-      "SPV_NV_shader_image_footprint",
-      "SPV_NV_shading_rate",
-      "SPV_NV_mesh_shader",
-      "SPV_NV_ray_tracing",
-      "SPV_EXT_fragment_invocation_density",
-  });
-}
-
-}  // namespace opt
-}  // namespace spvtools
diff --git a/source/opt/common_uniform_elim_pass.h b/source/opt/common_uniform_elim_pass.h
deleted file mode 100644
index e6ef69c..0000000
--- a/source/opt/common_uniform_elim_pass.h
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright (c) 2016 The Khronos Group Inc.
-// Copyright (c) 2016 Valve Corporation
-// Copyright (c) 2016 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_COMMON_UNIFORM_ELIM_PASS_H_
-#define SOURCE_OPT_COMMON_UNIFORM_ELIM_PASS_H_
-
-#include <algorithm>
-#include <list>
-#include <map>
-#include <memory>
-#include <queue>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "source/opt/basic_block.h"
-#include "source/opt/decoration_manager.h"
-#include "source/opt/def_use_manager.h"
-#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 CommonUniformElimPass : public Pass {
-  using cbb_ptr = const BasicBlock*;
-
- public:
-  using GetBlocksFunction =
-      std::function<std::vector<BasicBlock*>*(const BasicBlock*)>;
-
-  CommonUniformElimPass();
-
-  const char* name() const override { return "eliminate-common-uniform"; }
-  Status Process() override;
-
- private:
-  // Returns true if |opcode| is a non-ptr access chain op
-  bool IsNonPtrAccessChain(const SpvOp opcode) const;
-
-  // Returns true if |typeInst| is a sampler or image type or a struct
-  // containing one, recursively.
-  bool IsSamplerOrImageType(const Instruction* typeInst) const;
-
-  // Returns true if |varId| is a variable containing a sampler or image.
-  bool IsSamplerOrImageVar(uint32_t varId) const;
-
-  // Given a load or store pointed at by |ip|, return the top-most
-  // non-CopyObj in its pointer operand. Also return the base pointer
-  // in |objId|.
-  Instruction* GetPtr(Instruction* ip, uint32_t* objId);
-
-  // Return true if variable is uniform
-  bool IsUniformVar(uint32_t varId);
-
-  // Given the type id for a struct type, checks if the struct type
-  // or any struct member is volatile decorated
-  bool IsVolatileStruct(uint32_t type_id);
-
-  // Given an OpAccessChain instruction, return true
-  // if the accessed variable belongs to a volatile
-  // decorated object or member of a struct type
-  bool IsAccessChainToVolatileStructType(const Instruction& AccessChainInst);
-
-  // Given an OpLoad instruction, return true if
-  // OpLoad has a Volatile Memory Access flag or if
-  // the resulting type is a volatile decorated struct
-  bool IsVolatileLoad(const Instruction& loadInst);
-
-  // Return true if any uses of |id| are decorate ops.
-  bool HasUnsupportedDecorates(uint32_t id) const;
-
-  // Return true if all uses of |id| are only name or decorate ops.
-  bool HasOnlyNamesAndDecorates(uint32_t id) const;
-
-  // Delete inst if it has no uses. Assumes inst has a resultId.
-  void DeleteIfUseless(Instruction* inst);
-
-  // Replace all instances of load's id with replId and delete load
-  // and its access chain, if any
-  Instruction* ReplaceAndDeleteLoad(Instruction* loadInst, uint32_t replId,
-                                    Instruction* ptrInst);
-
-  // For the (constant index) access chain ptrInst, create an
-  // equivalent load and extract
-  void GenACLoadRepl(const Instruction* ptrInst,
-                     std::vector<std::unique_ptr<Instruction>>* newInsts,
-                     uint32_t* resultId);
-
-  // Return true if all indices are constant
-  bool IsConstantIndexAccessChain(Instruction* acp);
-
-  // Convert all uniform access chain loads into load/extract.
-  bool UniformAccessChainConvert(Function* func);
-
-  // Compute structured successors for function |func|.
-  // A block's structured successors are the blocks it branches to
-  // together with its declared merge block if it has one.
-  // When order matters, the merge block always appears first.
-  // This assures correct depth first search in the presence of early
-  // returns and kills. If the successor vector contain duplicates
-  // if the merge block, they are safely ignored by DFS.
-  //
-  // TODO(dnovillo): This pass computes structured successors slightly different
-  // than the implementation in class Pass. Can this be re-factored?
-  void ComputeStructuredSuccessors(Function* func);
-
-  // Compute structured block order for |func| into |structuredOrder|. This
-  // order has the property that dominators come before all blocks they
-  // dominate and merge blocks come after all blocks that are in the control
-  // constructs of their header.
-  //
-  // TODO(dnovillo): This pass computes structured order slightly different
-  // than the implementation in class Pass. Can this be re-factored?
-  void ComputeStructuredOrder(Function* func, std::list<BasicBlock*>* order);
-
-  // Eliminate loads of uniform variables which have previously been loaded.
-  // If first load is in control flow, move it to first block of function.
-  // Most effective if preceded by UniformAccessChainRemoval().
-  bool CommonUniformLoadElimination(Function* func);
-
-  // Eliminate loads of uniform sampler and image variables which have
-  // previously
-  // been loaded in the same block for types whose loads cannot cross blocks.
-  bool CommonUniformLoadElimBlock(Function* func);
-
-  // Eliminate duplicated extracts of same id. Extract may be moved to same
-  // block as the id definition. This is primarily intended for extracts
-  // from uniform loads. Most effective if preceded by
-  // CommonUniformLoadElimination().
-  bool CommonExtractElimination(Function* func);
-
-  // For function |func|, first change all uniform constant index
-  // access chain loads into equivalent composite extracts. Then consolidate
-  // identical uniform loads into one uniform load. Finally, consolidate
-  // identical uniform extracts into one uniform extract. This may require
-  // moving a load or extract to a point which dominates all uses.
-  // Return true if func is modified.
-  //
-  // This pass requires the function to have structured control flow ie shader
-  // capability. It also requires logical addressing ie Addresses capability
-  // is not enabled. It also currently does not support any extensions.
-  //
-  // This function currently only optimizes loads with a single index.
-  bool EliminateCommonUniform(Function* func);
-
-  // Initialize extensions whitelist
-  void InitExtensions();
-
-  // Return true if all extensions in this module are allowed by this pass.
-  bool AllExtensionsSupported() const;
-
-  // Return true if |op| is a decorate for non-type instruction
-  inline bool IsNonTypeDecorate(uint32_t op) const {
-    return (op == SpvOpDecorate || op == SpvOpDecorateId);
-  }
-
-  // Return true if |inst| is an instruction that loads uniform variable and
-  // can be replaced with other uniform load instruction.
-  bool IsUniformLoadToBeRemoved(Instruction* inst) {
-    if (inst->opcode() == SpvOpLoad) {
-      uint32_t varId;
-      Instruction* ptrInst = GetPtr(inst, &varId);
-      if (ptrInst->opcode() == SpvOpVariable && IsUniformVar(varId) &&
-          !IsSamplerOrImageVar(varId) &&
-          !HasUnsupportedDecorates(inst->result_id()) && !IsVolatileLoad(*inst))
-        return true;
-    }
-    return false;
-  }
-
-  void Initialize();
-  Pass::Status ProcessImpl();
-
-  // Map from uniform variable id to its common load id
-  std::unordered_map<uint32_t, uint32_t> uniform2load_id_;
-
-  // Map of extract composite ids to map of indices to insts
-  // TODO(greg-lunarg): Consider std::vector.
-  std::unordered_map<uint32_t,
-                     std::unordered_map<uint32_t, std::list<Instruction*>>>
-      comp2idx2inst_;
-
-  // Extensions supported by this pass.
-  std::unordered_set<std::string> extensions_whitelist_;
-
-  // Map from block to its structured successor blocks. See
-  // ComputeStructuredSuccessors() for definition.
-  std::unordered_map<const BasicBlock*, std::vector<BasicBlock*>>
-      block2structured_succs_;
-};
-
-}  // namespace opt
-}  // namespace spvtools
-
-#endif  // SOURCE_OPT_COMMON_UNIFORM_ELIM_PASS_H_
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index f6013a3..10fcde4 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -408,6 +408,28 @@
   };
 }
 
+// This defines a |UnaryScalarFoldingRule| that performs |OpQuantizeToF16|.
+UnaryScalarFoldingRule FoldQuantizeToF16Scalar() {
+  return [](const analysis::Type* result_type, const analysis::Constant* a,
+            analysis::ConstantManager* const_mgr) -> const analysis::Constant* {
+    assert(result_type != nullptr && a != nullptr);
+    const analysis::Float* float_type = a->type()->AsFloat();
+    assert(float_type != nullptr);
+    if (float_type->width() != 32) {
+      return nullptr;
+    }
+
+    float fa = a->GetFloat();
+    utils::HexFloat<utils::FloatProxy<float>> orignal(fa);
+    utils::HexFloat<utils::FloatProxy<utils::Float16>> quantized(0);
+    utils::HexFloat<utils::FloatProxy<float>> result(0.0f);
+    orignal.castTo(quantized, utils::round_direction::kToZero);
+    quantized.castTo(result, utils::round_direction::kToZero);
+    std::vector<uint32_t> words = {result.getBits()};
+    return const_mgr->GetConstant(result_type, words);
+  };
+}
+
 // This macro defines a |BinaryScalarFoldingRule| that applies |op|.  The
 // operator |op| must work for both float and double, and use syntax "f1 op f2".
 #define FOLD_FPARITH_OP(op)                                                \
@@ -438,6 +460,9 @@
 // Define the folding rule for conversion between floating point and integer
 ConstantFoldingRule FoldFToI() { return FoldFPUnaryOp(FoldFToIOp()); }
 ConstantFoldingRule FoldIToF() { return FoldFPUnaryOp(FoldIToFOp()); }
+ConstantFoldingRule FoldQuantizeToF16() {
+  return FoldFPUnaryOp(FoldQuantizeToF16Scalar());
+}
 
 // Define the folding rules for subtraction, addition, multiplication, and
 // division for floating point values.
@@ -582,13 +607,17 @@
     std::vector<uint32_t> words = result.GetWords();
     const analysis::Constant* result_const =
         const_mgr->GetConstant(float_type, words);
-    for (uint32_t i = 0; i < a_components.size(); ++i) {
+    for (uint32_t i = 0; i < a_components.size() && result_const != nullptr;
+         ++i) {
       if (a_components[i] == nullptr || b_components[i] == nullptr) {
         return nullptr;
       }
 
       const analysis::Constant* component = FOLD_FPARITH_OP(*)(
           new_type, a_components[i], b_components[i], const_mgr);
+      if (component == nullptr) {
+        return nullptr;
+      }
       result_const =
           FOLD_FPARITH_OP(+)(new_type, result_const, component, const_mgr);
     }
@@ -844,6 +873,7 @@
   rules_[SpvOpVectorTimesScalar].push_back(FoldVectorTimesScalar());
 
   rules_[SpvOpFNegate].push_back(FoldFNegate());
+  rules_[SpvOpQuantizeToF16].push_back(FoldQuantizeToF16());
 }
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 768364b..3c05f9e 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -167,6 +167,10 @@
     const Constant* new_const, Module::inst_iterator* pos, uint32_t type_id) {
   // TODO(1841): Handle id overflow.
   uint32_t new_id = context()->TakeNextId();
+  if (new_id == 0) {
+    return nullptr;
+  }
+
   auto new_inst = CreateInstruction(new_id, new_const, type_id);
   if (!new_inst) {
     return nullptr;
@@ -181,8 +185,6 @@
 
 Instruction* ConstantManager::GetDefiningInstruction(
     const Constant* c, uint32_t type_id, Module::inst_iterator* pos) {
-  assert(type_id == 0 ||
-         context()->get_type_mgr()->GetType(type_id) == c->type());
   uint32_t decl_id = FindDeclaredConstant(c, type_id);
   if (decl_id == 0) {
     auto iter = context()->types_values_end();
diff --git a/source/opt/constants.h b/source/opt/constants.h
index de2dfc3..a8e0fb5 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -432,7 +432,7 @@
   std::unique_ptr<Constant> Copy() const override {
     return std::unique_ptr<Constant>(CopyNullConstant().release());
   }
-  bool IsZero() const override { return true; };
+  bool IsZero() const override { return true; }
 };
 
 // Hash function for Constant instances. Use the structure of the constant as
@@ -524,12 +524,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, it is used as the type of the constant when a new
-  // instruction is created. Otherwise the type of the constant is derived by
-  // getting an id from the type manager for |c|.
-  //
-  // When |type_id| is not zero, the type of |c| must be the type returned by
-  // type manager when given |type_id|.
+  // |type_id| is specified, the contant returned will have that type id.
   Instruction* GetDefiningInstruction(const Constant* c, uint32_t type_id = 0,
                                       Module::inst_iterator* pos = nullptr);
 
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index e508c05..751786c 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -563,11 +563,6 @@
 }
 void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
                                      Instruction* new_ptr_inst) {
-  // TODO (s-perron): Keep the def-use manager up to date.  Not done now because
-  // it can cause problems for the |ForEachUse| traversals.  Can be use by
-  // keeping a list of instructions that need updating, and then updating them
-  // in |PropagateObject|.
-
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -703,74 +698,6 @@
   }
 }
 
-uint32_t CopyPropagateArrays::GenerateCopy(Instruction* object_inst,
-                                           uint32_t new_type_id,
-                                           Instruction* insertion_position) {
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
-  uint32_t original_type_id = object_inst->type_id();
-  if (original_type_id == new_type_id) {
-    return object_inst->result_id();
-  }
-
-  InstructionBuilder ir_builder(
-      context(), insertion_position,
-      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
-
-  analysis::Type* original_type = type_mgr->GetType(original_type_id);
-  analysis::Type* new_type = type_mgr->GetType(new_type_id);
-
-  if (const analysis::Array* original_array_type = original_type->AsArray()) {
-    uint32_t original_element_type_id =
-        type_mgr->GetId(original_array_type->element_type());
-
-    analysis::Array* new_array_type = new_type->AsArray();
-    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
-    uint32_t new_element_type_id =
-        type_mgr->GetId(new_array_type->element_type());
-
-    std::vector<uint32_t> element_ids;
-    const analysis::Constant* length_const =
-        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
-    assert(length_const->AsIntConstant());
-    uint32_t array_length = length_const->AsIntConstant()->GetU32();
-    for (uint32_t i = 0; i < array_length; i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          original_element_type_id, object_inst->result_id(), {i});
-      element_ids.push_back(
-          GenerateCopy(extract, new_element_type_id, insertion_position));
-    }
-
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else if (const analysis::Struct* original_struct_type =
-                 original_type->AsStruct()) {
-    analysis::Struct* new_struct_type = new_type->AsStruct();
-
-    const std::vector<const analysis::Type*>& original_types =
-        original_struct_type->element_types();
-    const std::vector<const analysis::Type*>& new_types =
-        new_struct_type->element_types();
-    std::vector<uint32_t> element_ids;
-    for (uint32_t i = 0; i < original_types.size(); i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
-      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
-                                         insertion_position));
-    }
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else {
-    // If we do not have an aggregate type, then we have a problem.  Either we
-    // found multiple instances of the same type, or we are copying to an
-    // incompatible type.  Either way the code is illegal.
-    assert(false &&
-           "Don't know how to copy this type.  Code is likely illegal.");
-  }
-  return 0;
-}
-
 uint32_t CopyPropagateArrays::GetMemberTypeId(
     uint32_t id, const std::vector<uint32_t>& access_chain) const {
   for (uint32_t element_index : access_chain) {
diff --git a/source/opt/copy_prop_arrays.h b/source/opt/copy_prop_arrays.h
index eb7cc68..f4314a7 100644
--- a/source/opt/copy_prop_arrays.h
+++ b/source/opt/copy_prop_arrays.h
@@ -217,12 +217,6 @@
   // |original_ptr_inst| to |type_id| and still have valid code.
   bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id);
 
-  // Returns the id whose value is the same as |object_to_copy| except its type
-  // is |new_type_id|.  Any instructions need to generate this value will be
-  // inserted before |insertion_position|.
-  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
-                        Instruction* insertion_position);
-
   // Returns a store to |var_inst| that writes to the entire variable, and is
   // the only store that does so.  Note it does not look through OpAccessChain
   // instruction, so partial stores are not considered.
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 9893536..8425a39 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -93,9 +93,8 @@
 
 bool DeadBranchElimPass::MarkLiveBlocks(
     Function* func, std::unordered_set<BasicBlock*>* live_blocks) {
-  StructuredCFGAnalysis* cfgAnalysis = context()->GetStructuredCFGAnalysis();
-
-  std::unordered_set<BasicBlock*> continues;
+  std::vector<std::pair<BasicBlock*, uint32_t>> conditions_to_simplify;
+  std::unordered_set<BasicBlock*> blocks_with_backedge;
   std::vector<BasicBlock*> stack;
   stack.push_back(&*func->begin());
   bool modified = false;
@@ -107,7 +106,10 @@
     if (!live_blocks->insert(block).second) continue;
 
     uint32_t cont_id = block->ContinueBlockIdIfAny();
-    if (cont_id != 0) continues.insert(GetParentBlock(cont_id));
+    if (cont_id != 0) {
+      AddBlocksWithBackEdge(cont_id, block->id(), block->MergeBlockIdIfAny(),
+                            &blocks_with_backedge);
+    }
 
     Instruction* terminator = block->terminator();
     uint32_t live_lab_id = 0;
@@ -146,35 +148,27 @@
       }
     }
 
-    // Don't simplify branches of continue blocks. A path from the continue to
-    // the header is required.
-    // TODO(alan-baker): They can be simplified iff there remains a path to the
-    // backedge. Structured control flow should guarantee one path hits the
-    // backedge, but I've removed the requirement for structured control flow
-    // from this pass.
-    bool simplify = live_lab_id != 0 && !continues.count(block);
+    // Don't simplify back edges unless it becomes a branch to the header. Every
+    // loop must have exactly one back edge to the loop header, so we cannot
+    // remove it.
+    bool simplify = false;
+    if (live_lab_id != 0) {
+      if (!blocks_with_backedge.count(block)) {
+        // This is not a back edge.
+        simplify = true;
+      } else {
+        const auto& struct_cfg_analysis = context()->GetStructuredCFGAnalysis();
+        uint32_t header_id = struct_cfg_analysis->ContainingLoop(block->id());
+        if (live_lab_id == header_id) {
+          // The new branch will be a branch to the header.
+          simplify = true;
+        }
+      }
+    }
 
     if (simplify) {
       modified = true;
-      // Replace with unconditional branch.
-      // Remove the merge instruction if it is a selection merge.
-      AddBranch(live_lab_id, block);
-      context()->KillInst(terminator);
-      Instruction* mergeInst = block->GetMergeInst();
-      if (mergeInst && mergeInst->opcode() == SpvOpSelectionMerge) {
-        Instruction* first_break = FindFirstExitFromSelectionMerge(
-            live_lab_id, mergeInst->GetSingleWordInOperand(0),
-            cfgAnalysis->LoopMergeBlock(live_lab_id),
-            cfgAnalysis->LoopContinueBlock(live_lab_id));
-        if (first_break == nullptr) {
-          context()->KillInst(mergeInst);
-        } else {
-          mergeInst->RemoveFromList();
-          first_break->InsertBefore(std::unique_ptr<Instruction>(mergeInst));
-          context()->set_instr_block(mergeInst,
-                                     context()->get_instr_block(first_break));
-        }
-      }
+      conditions_to_simplify.push_back({block, live_lab_id});
       stack.push_back(GetParentBlock(live_lab_id));
     } else {
       // All successors are live.
@@ -185,9 +179,60 @@
     }
   }
 
+  // Traverse |conditions_to_simplify in reverse order.  This is done so that we
+  // simplify nested constructs before simplifying the constructs that contain
+  // them.
+  for (auto b = conditions_to_simplify.rbegin();
+       b != conditions_to_simplify.rend(); ++b) {
+    SimplifyBranch(b->first, b->second);
+  }
+
   return modified;
 }
 
+void DeadBranchElimPass::SimplifyBranch(BasicBlock* block,
+                                        uint32_t live_lab_id) {
+  Instruction* merge_inst = block->GetMergeInst();
+  Instruction* terminator = block->terminator();
+  if (merge_inst && merge_inst->opcode() == SpvOpSelectionMerge) {
+    if (merge_inst->NextNode()->opcode() == SpvOpSwitch &&
+        SwitchHasNestedBreak(block->id())) {
+      // We have to keep the switch because it has a nest break, so we
+      // remove all cases except for the live one.
+      Instruction::OperandList new_operands;
+      new_operands.push_back(terminator->GetInOperand(0));
+      new_operands.push_back({SPV_OPERAND_TYPE_ID, {live_lab_id}});
+      terminator->SetInOperands(move(new_operands));
+      context()->UpdateDefUse(terminator);
+    } else {
+      // Check if the merge instruction is still needed because of a
+      // non-nested break from the construct.  Move the merge instruction if
+      // it is still needed.
+      StructuredCFGAnalysis* cfg_analysis =
+          context()->GetStructuredCFGAnalysis();
+      Instruction* first_break = FindFirstExitFromSelectionMerge(
+          live_lab_id, merge_inst->GetSingleWordInOperand(0),
+          cfg_analysis->LoopMergeBlock(live_lab_id),
+          cfg_analysis->LoopContinueBlock(live_lab_id),
+          cfg_analysis->SwitchMergeBlock(live_lab_id));
+
+      AddBranch(live_lab_id, block);
+      context()->KillInst(terminator);
+      if (first_break == nullptr) {
+        context()->KillInst(merge_inst);
+      } else {
+        merge_inst->RemoveFromList();
+        first_break->InsertBefore(std::unique_ptr<Instruction>(merge_inst));
+        context()->set_instr_block(merge_inst,
+                                   context()->get_instr_block(first_break));
+      }
+    }
+  } else {
+    AddBranch(live_lab_id, block);
+    context()->KillInst(terminator);
+  }
+}
+
 void DeadBranchElimPass::MarkUnreachableStructuredTargets(
     const std::unordered_set<BasicBlock*>& live_blocks,
     std::unordered_set<BasicBlock*>* unreachable_merges,
@@ -323,21 +368,7 @@
     const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) {
   bool modified = false;
   for (auto ebi = func->begin(); ebi != func->end();) {
-    if (unreachable_merges.count(&*ebi)) {
-      if (ebi->begin() != ebi->tail() ||
-          ebi->terminator()->opcode() != SpvOpUnreachable) {
-        // Make unreachable, but leave the label.
-        KillAllInsts(&*ebi, false);
-        // Add unreachable terminator.
-        ebi->AddInstruction(
-            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
-                                    std::initializer_list<Operand>{}));
-        context()->AnalyzeUses(ebi->terminator());
-        context()->set_instr_block(ebi->terminator(), &*ebi);
-        modified = true;
-      }
-      ++ebi;
-    } else if (unreachable_continues.count(&*ebi)) {
+    if (unreachable_continues.count(&*ebi)) {
       uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id();
       if (ebi->begin() != ebi->tail() ||
           ebi->terminator()->opcode() != SpvOpBranch ||
@@ -354,6 +385,20 @@
         modified = true;
       }
       ++ebi;
+    } else if (unreachable_merges.count(&*ebi)) {
+      if (ebi->begin() != ebi->tail() ||
+          ebi->terminator()->opcode() != SpvOpUnreachable) {
+        // Make unreachable, but leave the label.
+        KillAllInsts(&*ebi, false);
+        // Add unreachable terminator.
+        ebi->AddInstruction(
+            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
+                                    std::initializer_list<Operand>{}));
+        context()->AnalyzeUses(ebi->terminator());
+        context()->set_instr_block(ebi->terminator(), &*ebi);
+        modified = true;
+      }
+      ++ebi;
     } else if (!live_blocks.count(&*ebi)) {
       // Kill this block.
       KillAllInsts(&*ebi);
@@ -442,11 +487,12 @@
 
 Instruction* DeadBranchElimPass::FindFirstExitFromSelectionMerge(
     uint32_t start_block_id, uint32_t merge_block_id, uint32_t loop_merge_id,
-    uint32_t loop_continue_id) {
+    uint32_t loop_continue_id, uint32_t switch_merge_id) {
   // To find the "first" exit, we follow branches looking for a conditional
   // branch that is not in a nested construct and is not the header of a new
   // construct.  We follow the control flow from |start_block_id| to find the
   // first one.
+
   while (start_block_id != merge_block_id && start_block_id != loop_merge_id &&
          start_block_id != loop_continue_id) {
     BasicBlock* start_block = context()->get_instr_block(start_block_id);
@@ -470,6 +516,11 @@
               next_block_id = branch->GetSingleWordInOperand(3 - i);
               break;
             }
+            if (branch->GetSingleWordInOperand(i) == switch_merge_id &&
+                switch_merge_id != merge_block_id) {
+              next_block_id = branch->GetSingleWordInOperand(3 - i);
+              break;
+            }
           }
 
           if (next_block_id == 0) {
@@ -480,11 +531,15 @@
       case SpvOpSwitch:
         next_block_id = start_block->MergeBlockIdIfAny();
         if (next_block_id == 0) {
-          // A switch with no merge instructions can have at most 4 targets:
+          // A switch with no merge instructions can have at most 5 targets:
           //   a. |merge_block_id|
           //   b. |loop_merge_id|
           //   c. |loop_continue_id|
-          //   d. 1 block inside the current region.
+          //   d. |switch_merge_id|
+          //   e. 1 block inside the current region.
+          //
+          // Note that because this is a switch, |merge_block_id| must equal
+          // |switch_merge_id|.
           //
           // This leads to a number of cases of what to do.
           //
@@ -498,7 +553,6 @@
           //
           // 3.  Otherwise, this branch may break, but not to the current merge
           // block.  So we continue with the block that is inside the loop.
-
           bool found_break = false;
           for (uint32_t i = 1; i < branch->NumInOperands(); i += 2) {
             uint32_t target = branch->GetSingleWordInOperand(i);
@@ -538,5 +592,60 @@
   return nullptr;
 }
 
+void DeadBranchElimPass::AddBlocksWithBackEdge(
+    uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+    std::unordered_set<BasicBlock*>* blocks_with_back_edges) {
+  std::unordered_set<uint32_t> visited;
+  visited.insert(cont_id);
+  visited.insert(header_id);
+  visited.insert(merge_id);
+
+  std::vector<uint32_t> work_list;
+  work_list.push_back(cont_id);
+
+  while (!work_list.empty()) {
+    uint32_t bb_id = work_list.back();
+    work_list.pop_back();
+
+    BasicBlock* bb = context()->get_instr_block(bb_id);
+
+    bool has_back_edge = false;
+    bb->ForEachSuccessorLabel([header_id, &visited, &work_list,
+                               &has_back_edge](uint32_t* succ_label_id) {
+      if (visited.insert(*succ_label_id).second) {
+        work_list.push_back(*succ_label_id);
+      }
+      if (*succ_label_id == header_id) {
+        has_back_edge = true;
+      }
+    });
+
+    if (has_back_edge) {
+      blocks_with_back_edges->insert(bb);
+    }
+  }
+}
+
+bool DeadBranchElimPass::SwitchHasNestedBreak(uint32_t switch_header_id) {
+  std::vector<BasicBlock*> block_in_construct;
+  BasicBlock* start_block = context()->get_instr_block(switch_header_id);
+  uint32_t merge_block_id = start_block->MergeBlockIdIfAny();
+
+  StructuredCFGAnalysis* cfg_analysis = context()->GetStructuredCFGAnalysis();
+  return !get_def_use_mgr()->WhileEachUser(
+      merge_block_id,
+      [this, cfg_analysis, switch_header_id](Instruction* inst) {
+        if (!inst->IsBranch()) {
+          return true;
+        }
+
+        BasicBlock* bb = context()->get_instr_block(inst);
+        if (bb->id() == switch_header_id) {
+          return true;
+        }
+        return (cfg_analysis->ContainingConstruct(inst) == switch_header_id);
+      });
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index 4a1b6b4..a50933f 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -147,7 +147,26 @@
   Instruction* FindFirstExitFromSelectionMerge(uint32_t start_block_id,
                                                uint32_t merge_block_id,
                                                uint32_t loop_merge_id,
-                                               uint32_t loop_continue_id);
+                                               uint32_t loop_continue_id,
+                                               uint32_t switch_merge_id);
+
+  // Adds to |blocks_with_back_edges| all of the blocks on the path from the
+  // basic block |cont_id| to |header_id| and |merge_id|.  The intention is that
+  // |cond_id| is a the continue target of a loop, |header_id| is the header of
+  // the loop, and |merge_id| is the merge block of the loop.
+  void AddBlocksWithBackEdge(
+      uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+      std::unordered_set<BasicBlock*>* blocks_with_back_edges);
+
+  // Returns true if there is a brach to the merge node of the selection
+  // construct |switch_header_id| that is inside a nested selection construct.
+  bool SwitchHasNestedBreak(uint32_t switch_header_id);
+
+  // Replaces the terminator of |block| with a branch to |live_lab_id|.  The
+  // merge instruction is deleted or moved as needed to maintain structured
+  // control flow.  Assumes that the StructuredCFGAnalysis is valid for the
+  // constructs containing |block|.
+  void SimplifyBranch(BasicBlock* block, uint32_t live_lab_id);
 };
 
 }  // namespace opt
diff --git a/source/opt/decompose_initialized_variables_pass.cpp b/source/opt/decompose_initialized_variables_pass.cpp
new file mode 100644
index 0000000..875bf7e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/decompose_initialized_variables_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool HasInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+  if (inst->NumOperands() < 4) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status DecomposeInitializedVariablesPass::Process() {
+  auto* module = context()->module();
+  std::unordered_set<Instruction*> changed;
+
+  std::vector<std::tuple<uint32_t, uint32_t>> global_stores;
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+    if (!HasInitializer(inst)) continue;
+
+    auto var_id = inst->result_id();
+    auto val_id = inst->GetOperand(3).words[0];
+    global_stores.push_back(std::make_tuple(var_id, val_id));
+    iter->RemoveOperand(3);
+    changed.insert(&*iter);
+  }
+
+  std::unordered_set<uint32_t> entry_ids;
+  for (auto entry = module->entry_points().begin();
+       entry != module->entry_points().end(); ++entry) {
+    entry_ids.insert(entry->GetSingleWordInOperand(1));
+  }
+
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    std::vector<Instruction*> function_stores;
+    auto first_block = func->entry().get();
+    inst_iterator insert_point = first_block->begin();
+    for (auto iter = first_block->begin();
+         iter != first_block->end() && iter->opcode() == SpvOpVariable;
+         ++iter) {
+      // For valid SPIRV-V, there is guaranteed to be at least one instruction
+      // after the OpVariable instructions.
+      insert_point = (*iter).NextNode();
+      Instruction* inst = &(*iter);
+      if (!HasInitializer(inst)) continue;
+
+      auto var_id = inst->result_id();
+      auto val_id = inst->GetOperand(3).words[0];
+      Instruction* store_inst = new Instruction(
+          context(), SpvOpStore, 0, 0,
+          {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+      function_stores.push_back(store_inst);
+      iter->RemoveOperand(3);
+      changed.insert(&*iter);
+    }
+
+    if (entry_ids.find(func->result_id()) != entry_ids.end()) {
+      for (auto store_ids : global_stores) {
+        uint32_t var_id;
+        uint32_t val_id;
+        std::tie(var_id, val_id) = store_ids;
+        auto* store_inst = new Instruction(
+            context(), SpvOpStore, 0, 0,
+            {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+        context()->set_instr_block(store_inst, &*first_block);
+        first_block->AddInstruction(std::unique_ptr<Instruction>(store_inst));
+        store_inst->InsertBefore(&*insert_point);
+        changed.insert(store_inst);
+      }
+    }
+
+    for (auto store = function_stores.begin(); store != function_stores.end();
+         ++store) {
+      context()->set_instr_block(*store, first_block);
+      (*store)->InsertBefore(&*insert_point);
+      changed.insert(*store);
+    }
+  }
+
+  auto* def_use_mgr = get_def_use_mgr();
+  for (auto* inst : changed) def_use_mgr->UpdateDefUse(inst);
+
+  return !changed.empty() ? Pass::Status::SuccessWithChange
+                          : Pass::Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/decompose_initialized_variables_pass.h b/source/opt/decompose_initialized_variables_pass.h
new file mode 100644
index 0000000..c0bd35e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+#define SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts variable declartions with initializers into seperate declaration and
+// assignment statements. This is done due to known issues with some Vulkan
+// implementations' handling of initialized variables.
+//
+// Only decomposes variables with storage classes that are valid in Vulkan
+// execution environments; Output, Private, and Function.
+// Currently only Function is implemented.
+class DecomposeInitializedVariablesPass : public Pass {
+ public:
+  const char* name() const override {
+    return "decompose-initialized-variables";
+  }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index a12326b..a10c992 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -22,6 +22,33 @@
 
 #include "source/opt/ir_context.h"
 
+namespace {
+using InstructionVector = std::vector<const spvtools::opt::Instruction*>;
+using DecorationSet = std::set<std::u32string>;
+
+// Returns true if |a| is a subet of |b|.
+bool IsSubset(const DecorationSet& a, const DecorationSet& b) {
+  auto it1 = a.begin();
+  auto it2 = b.begin();
+
+  while (it1 != a.end()) {
+    if (it2 == b.end() || *it1 < *it2) {
+      // |*it1| is in |a|, but not in |b|.
+      return false;
+    }
+    if (*it1 == *it2) {
+      // Found the element move to the next one.
+      it1++;
+      it2++;
+    } else /* *it1 > *it2 */ {
+      // Did not find |*it1| yet, check the next element in |b|.
+      it2++;
+    }
+  }
+  return true;
+}
+}  // namespace
+
 namespace spvtools {
 namespace opt {
 namespace analysis {
@@ -160,18 +187,15 @@
 
 bool DecorationManager::HaveTheSameDecorations(uint32_t id1,
                                                uint32_t id2) const {
-  using InstructionList = std::vector<const Instruction*>;
-  using DecorationSet = std::set<std::u32string>;
-
-  const InstructionList decorations_for1 = GetDecorationsFor(id1, false);
-  const InstructionList decorations_for2 = GetDecorationsFor(id2, false);
+  const InstructionVector decorations_for1 = GetDecorationsFor(id1, false);
+  const InstructionVector decorations_for2 = GetDecorationsFor(id2, false);
 
   // This function splits the decoration instructions into different sets,
   // based on their opcode; only OpDecorate, OpDecorateId,
   // OpDecorateStringGOOGLE, and OpMemberDecorate are considered, the other
   // opcodes are ignored.
   const auto fillDecorationSets =
-      [](const InstructionList& decoration_list, DecorationSet* decorate_set,
+      [](const InstructionVector& decoration_list, DecorationSet* decorate_set,
          DecorationSet* decorate_id_set, DecorationSet* decorate_string_set,
          DecorationSet* member_decorate_set) {
         for (const Instruction* inst : decoration_list) {
@@ -227,6 +251,73 @@
   return result;
 }
 
+bool DecorationManager::HaveSubsetOfDecorations(uint32_t id1,
+                                                uint32_t id2) const {
+  const InstructionVector decorations_for1 = GetDecorationsFor(id1, false);
+  const InstructionVector decorations_for2 = GetDecorationsFor(id2, false);
+
+  // This function splits the decoration instructions into different sets,
+  // based on their opcode; only OpDecorate, OpDecorateId,
+  // OpDecorateStringGOOGLE, and OpMemberDecorate are considered, the other
+  // opcodes are ignored.
+  const auto fillDecorationSets =
+      [](const InstructionVector& decoration_list, DecorationSet* decorate_set,
+         DecorationSet* decorate_id_set, DecorationSet* decorate_string_set,
+         DecorationSet* member_decorate_set) {
+        for (const Instruction* inst : decoration_list) {
+          std::u32string decoration_payload;
+          // Ignore the opcode and the target as we do not want them to be
+          // compared.
+          for (uint32_t i = 1u; i < inst->NumInOperands(); ++i) {
+            for (uint32_t word : inst->GetInOperand(i).words) {
+              decoration_payload.push_back(word);
+            }
+          }
+
+          switch (inst->opcode()) {
+            case SpvOpDecorate:
+              decorate_set->emplace(std::move(decoration_payload));
+              break;
+            case SpvOpMemberDecorate:
+              member_decorate_set->emplace(std::move(decoration_payload));
+              break;
+            case SpvOpDecorateId:
+              decorate_id_set->emplace(std::move(decoration_payload));
+              break;
+            case SpvOpDecorateStringGOOGLE:
+              decorate_string_set->emplace(std::move(decoration_payload));
+              break;
+            default:
+              break;
+          }
+        }
+      };
+
+  DecorationSet decorate_set_for1;
+  DecorationSet decorate_id_set_for1;
+  DecorationSet decorate_string_set_for1;
+  DecorationSet member_decorate_set_for1;
+  fillDecorationSets(decorations_for1, &decorate_set_for1,
+                     &decorate_id_set_for1, &decorate_string_set_for1,
+                     &member_decorate_set_for1);
+
+  DecorationSet decorate_set_for2;
+  DecorationSet decorate_id_set_for2;
+  DecorationSet decorate_string_set_for2;
+  DecorationSet member_decorate_set_for2;
+  fillDecorationSets(decorations_for2, &decorate_set_for2,
+                     &decorate_id_set_for2, &decorate_string_set_for2,
+                     &member_decorate_set_for2);
+
+  const bool result =
+      IsSubset(decorate_set_for1, decorate_set_for2) &&
+      IsSubset(decorate_id_set_for1, decorate_id_set_for2) &&
+      IsSubset(member_decorate_set_for1, member_decorate_set_for2) &&
+      // Compare string sets last in case the strings are long.
+      IsSubset(decorate_string_set_for1, decorate_string_set_for2);
+  return result;
+}
+
 // TODO(pierremoreau): If OpDecorateId is referencing an OpConstant, one could
 //                     check that the constants are the same rather than just
 //                     looking at the constant ID.
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index a5fb4c8..01244f2 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -74,6 +74,12 @@
   // instructions that apply the same decorations but to different IDs, still
   // count as being the same.
   bool HaveTheSameDecorations(uint32_t id1, uint32_t id2) const;
+
+  // Returns whether two IDs have the same decorations. Two SpvOpGroupDecorate
+  // instructions that apply the same decorations but to different IDs, still
+  // count as being the same.
+  bool HaveSubsetOfDecorations(uint32_t id1, uint32_t id2) const;
+
   // Returns whether the two decorations instructions are the same and are
   // applying the same decorations; unless |ignore_target| is false, the targets
   // to which they are applied to does not matter, except for the member part.
diff --git a/source/opt/eliminate_dead_functions_pass.cpp b/source/opt/eliminate_dead_functions_pass.cpp
index f067be5..a465521 100644
--- a/source/opt/eliminate_dead_functions_pass.cpp
+++ b/source/opt/eliminate_dead_functions_pass.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_functions_util.h"
 
 #include <unordered_set>
 
@@ -36,8 +37,8 @@
        funcIter != get_module()->end();) {
     if (live_function_set.count(&*funcIter) == 0) {
       modified = true;
-      EliminateFunction(&*funcIter);
-      funcIter = funcIter.Erase();
+      funcIter =
+          eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
     } else {
       ++funcIter;
     }
@@ -47,10 +48,5 @@
                   : Pass::Status::SuccessWithoutChange;
 }
 
-void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
-  // Remove all of the instruction in the function body
-  func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
-                    true);
-}
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
new file mode 100644
index 0000000..8a38959
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace eliminatedeadfunctionsutil {
+
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter) {
+  (*func_iter)
+      ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
+                    true);
+  return func_iter->Erase();
+}
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_util.h b/source/opt/eliminate_dead_functions_util.h
new file mode 100644
index 0000000..9fcce95
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functionality for eliminating functions that are not needed, for use
+// by various analyses and passes.
+namespace eliminatedeadfunctionsutil {
+
+// Removes all of the function's instructions, removes the function from the
+// module, and returns the next iterator.
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter);
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
new file mode 100644
index 0000000..0b73b2d
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -0,0 +1,637 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/eliminate_dead_members_pass.h"
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+const uint32_t kRemovedMember = 0xFFFFFFFF;
+}
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadMembersPass::Process() {
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return Status::SuccessWithoutChange;
+
+  FindLiveMembers();
+  if (RemoveDeadMembers()) {
+    return Status::SuccessWithChange;
+  }
+  return Status::SuccessWithoutChange;
+}
+
+void EliminateDeadMembersPass::FindLiveMembers() {
+  // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+  // we have to mark them as fully used just to be safe.
+  for (auto& inst : get_module()->types_values()) {
+    if (inst.opcode() == SpvOpSpecConstantOp) {
+      MarkTypeAsFullyUsed(inst.type_id());
+    } else if (inst.opcode() == SpvOpVariable) {
+      switch (inst.GetSingleWordInOperand(0)) {
+        case SpvStorageClassInput:
+        case SpvStorageClassOutput:
+          MarkPointeeTypeAsFullUsed(inst.type_id());
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  for (const Function& func : *get_module()) {
+    FindLiveMembers(func);
+  }
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
+  function.ForEachInst(
+      [this](const Instruction* inst) { FindLiveMembers(inst); });
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpStore:
+      MarkMembersAsLiveForStore(inst);
+      break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      MarkMembersAsLiveForCopyMemory(inst);
+      break;
+    case SpvOpCompositeExtract:
+      MarkMembersAsLiveForExtract(inst);
+      break;
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      MarkMembersAsLiveForAccessChain(inst);
+      break;
+    case SpvOpReturnValue:
+      // This should be an issue only if we are returning from the entry point.
+      // However, for now I will keep it more conservative because functions are
+      // often inlined leaving only the entry points.
+      MarkOperandTypeAsFullyUsed(inst, 0);
+      break;
+    case SpvOpArrayLength:
+      MarkMembersAsLiveForArrayLength(inst);
+      break;
+    case SpvOpLoad:
+    case SpvOpCompositeInsert:
+    case SpvOpCompositeConstruct:
+      break;
+    default:
+      // This path is here for safety.  All instructions that can reference
+      // structs in a function body should be handled above.  However, this will
+      // keep the pass valid, but not optimal, as new instructions get added
+      // or if something was missed.
+      MarkStructOperandsAsFullyUsed(inst);
+      break;
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
+    const Instruction* inst) {
+  // We should only have to mark the members as live if the store is to
+  // memory that is read outside of the shader.  Other passes can remove all
+  // store to memory that is not visible outside of the shader, so we do not
+  // complicate the code for now.
+  assert(inst->opcode() == SpvOpStore);
+  uint32_t object_id = inst->GetSingleWordInOperand(1);
+  Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
+  uint32_t object_type_id = object_inst->type_id();
+  MarkTypeAsFullyUsed(object_type_id);
+}
+
+void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  assert(type_inst != nullptr);
+  if (type_inst->opcode() != SpvOpTypeStruct) {
+    return;
+  }
+
+  // Mark every member of the current struct as used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    used_members_[type_id].insert(i);
+  }
+
+  // Mark any sub struct as fully used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
+  }
+}
+
+void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) {
+  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer);
+  MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1));
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
+    const Instruction* inst) {
+  uint32_t target_id = inst->GetSingleWordInOperand(0);
+  Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
+  uint32_t pointer_type_id = target_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  MarkTypeAsFullyUsed(type_id);
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCompositeExtract);
+
+  uint32_t composite_id = inst->GetSingleWordInOperand(0);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        used_members_[type_id].insert(member_idx);
+        type_id = type_inst->GetSingleWordInOperand(member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  // For a pointer access chain, we need to skip the |element| index.  It is not
+  // a reference to the member of a struct, and it does not change the type.
+  uint32_t i = (inst->opcode() == SpvOpAccessChain ||
+                        inst->opcode() == SpvOpInBoundsAccessChain
+                    ? 1
+                    : 2);
+  for (; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          used_members_[type_id].insert(member_idx->GetU32());
+          type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
+        } else {
+          used_members_[type_id].insert(
+              static_cast<uint32_t>(member_idx->GetU64()));
+          type_id = type_inst->GetSingleWordInOperand(
+              static_cast<uint32_t>(member_idx->GetU64()));
+        }
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
+    const Instruction* inst, uint32_t in_idx) {
+  uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
+  Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
+  MarkTypeAsFullyUsed(op_inst->type_id());
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpArrayLength);
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t pointer_type_id = object_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
+}
+
+bool EliminateDeadMembersPass::RemoveDeadMembers() {
+  bool modified = false;
+
+  // First update all of the OpTypeStruct instructions.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpTypeStruct:
+        modified |= UpdateOpTypeStruct(inst);
+        break;
+      default:
+        break;
+    }
+  });
+
+  // Now update all of the instructions that reference the OpTypeStructs.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemberName:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpMemberDecorate:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpGroupMemberDecorate:
+        modified |= UpdateOpGroupMemberDecorate(inst);
+        break;
+      case SpvOpSpecConstantComposite:
+      case SpvOpConstantComposite:
+      case SpvOpCompositeConstruct:
+        modified |= UpdateConstantComposite(inst);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        modified |= UpdateAccessChain(inst);
+        break;
+      case SpvOpCompositeExtract:
+        modified |= UpdateCompsiteExtract(inst);
+        break;
+      case SpvOpCompositeInsert:
+        modified |= UpdateCompositeInsert(inst);
+        break;
+      case SpvOpArrayLength:
+        modified |= UpdateOpArrayLength(inst);
+        break;
+      case SpvOpSpecConstantOp:
+        assert(false && "Not yet implemented.");
+        // with OpCompositeExtract, OpCompositeInsert
+        // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
+        // OpInBoundsPtrAccessChain
+        break;
+      default:
+        break;
+    }
+  });
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
+  assert(inst->opcode() == SpvOpTypeStruct);
+
+  const auto& live_members = used_members_[inst->result_id()];
+  if (live_members.size() == inst->NumInOperands()) {
+    return false;
+  }
+
+  Instruction::OperandList new_operands;
+  for (uint32_t idx : live_members) {
+    new_operands.emplace_back(inst->GetInOperand(idx));
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpMemberName ||
+         inst->opcode() == SpvOpMemberDecorate);
+
+  uint32_t type_id = inst->GetSingleWordInOperand(0);
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return false;
+  }
+
+  uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+
+  if (new_member_idx == kRemovedMember) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  if (new_member_idx == orig_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpGroupMemberDecorate);
+
+  bool modified = false;
+
+  Instruction::OperandList new_operands;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+    uint32_t type_id = inst->GetSingleWordInOperand(i);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+
+    if (new_member_idx == kRemovedMember) {
+      modified = true;
+      continue;
+    }
+
+    new_operands.emplace_back(inst->GetOperand(i));
+    if (new_member_idx != member_idx) {
+      new_operands.emplace_back(
+          Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetOperand(i + 1));
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+
+  if (new_operands.size() == 1) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
+  assert(inst->opcode() == SpvOpConstantComposite ||
+         inst->opcode() == SpvOpCompositeConstruct);
+  uint32_t type_id = inst->type_id();
+
+  bool modified = false;
+  Instruction::OperandList new_operands;
+  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+    uint32_t new_idx = GetNewMemberIndex(type_id, i);
+    if (new_idx == kRemovedMember) {
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetInOperand(i));
+    }
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+
+  // For pointer access chains we want to copy the element operand.
+  if (inst->opcode() == SpvOpPtrAccessChain ||
+      inst->opcode() == SpvOpInBoundsPtrAccessChain) {
+    new_operands.emplace_back(inst->GetInOperand(1));
+  }
+
+  for (uint32_t i = static_cast<uint32_t>(new_operands.size());
+       i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        uint32_t orig_member_idx;
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          orig_member_idx = member_idx->GetU32();
+        } else {
+          orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
+        }
+        uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+        assert(new_member_idx != kRemovedMember);
+        if (orig_member_idx != new_member_idx) {
+          InstructionBuilder ir_builder(
+              context(), inst,
+              IRContext::kAnalysisDefUse |
+                  IRContext::kAnalysisInstrToBlockMapping);
+          uint32_t const_id =
+              ir_builder.GetUintConstant(new_member_idx)->result_id();
+          new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
+          modified = true;
+        } else {
+          new_operands.emplace_back(inst->GetInOperand(i));
+        }
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        new_operands.emplace_back(inst->GetInOperand(i));
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+        break;
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
+                                                     uint32_t member_idx) {
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return member_idx;
+  }
+
+  auto current_member = live_members->second.find(member_idx);
+  if (current_member == live_members->second.end()) {
+    return kRemovedMember;
+  }
+
+  return static_cast<uint32_t>(
+      std::distance(live_members->second.begin(), current_member));
+}
+
+bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t type_id = object_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    assert(new_member_idx != kRemovedMember);
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
+                          inst->opcode() != SpvOpInBoundsPtrAccessChain));
+        // The type will have already been rewriten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
+  uint32_t composite_id = inst->GetSingleWordInOperand(1);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  new_operands.emplace_back(inst->GetInOperand(1));
+  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    if (new_member_idx == kRemovedMember) {
+      context()->KillInst(inst);
+      return true;
+    }
+
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
+  uint32_t struct_id = inst->GetSingleWordInOperand(0);
+  Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
+  uint32_t pointer_type_id = struct_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  uint32_t member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+  assert(new_member_idx != kRemovedMember);
+
+  if (member_idx == new_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
+    const Instruction* inst) {
+  if (inst->type_id() != 0) {
+    MarkTypeAsFullyUsed(inst->type_id());
+  }
+
+  inst->ForEachInId([this](const uint32_t* id) {
+    Instruction* instruction = get_def_use_mgr()->GetDef(*id);
+    if (instruction->type_id() != 0) {
+      MarkTypeAsFullyUsed(instruction->type_id());
+    }
+  });
+}
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_members_pass.h b/source/opt/eliminate_dead_members_pass.h
new file mode 100644
index 0000000..4feaa55
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/mem_pass.h"
+#include "source/opt/module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Remove unused members from structures.  The remaining members will remain at
+// the same offset.
+class EliminateDeadMembersPass : public MemPass {
+ public:
+  const char* name() const override { return "eliminate-dead-members"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping;
+  }
+
+ private:
+  // Populate |used_members_| with the member of structures that are live in the
+  // current context.
+  void FindLiveMembers();
+
+  // Add to |used_members_| the member of structures that are live in
+  // |function|.
+  void FindLiveMembers(const Function& function);
+  // Add to |used_members_| the member of structures that are live in |inst|.
+  void FindLiveMembers(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpStore|
+  // instruction |inst|.
+  void MarkMembersAsLiveForStore(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpCopyMemory*|
+  // instruction |inst|.
+  void MarkMembersAsLiveForCopyMemory(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the
+  // |OpCompositeExtract| instruction |inst|.
+  void MarkMembersAsLiveForExtract(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |Op*AccessChain|
+  // instruction |inst|.
+  void MarkMembersAsLiveForAccessChain(const Instruction* inst);
+
+  // Add the member referenced by the OpArrayLength instruction |inst| to
+  // |uses_members_|.
+  void MarkMembersAsLiveForArrayLength(const Instruction* inst);
+
+  // Remove dead members from structs and updates any instructions that need to
+  // be updated as a consequence.  Return true if something changed.
+  bool RemoveDeadMembers();
+
+  // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate|
+  // instruction, so it references the correct member after the struct is
+  // updated.  Return true if something changed.
+  bool UpdateOpMemberNameOrDecorate(Instruction* inst);
+
+  // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it
+  // references the correct member after the struct is updated.  Return true if
+  // something changed.
+  bool UpdateOpGroupMemberDecorate(Instruction* inst);
+
+  // Update the |OpTypeStruct| instruction |inst| my removing the members that
+  // are not live.  Return true if something changed.
+  bool UpdateOpTypeStruct(Instruction* inst);
+
+  // Update the |OpConstantComposite| instruction |inst| to match the change
+  // made to the type that was being generated.  Return true if something
+  // changed.
+  bool UpdateConstantComposite(Instruction* inst);
+
+  // Update the |Op*AccessChain| instruction |inst| to reference the correct
+  // members. All members referenced in the access chain must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateAccessChain(Instruction* inst);
+
+  // Update the |OpCompositeExtract| instruction |inst| to reference the correct
+  // members. All members referenced in the instruction must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateCompsiteExtract(Instruction* inst);
+
+  // Update the |OpCompositeInsert| instruction |inst| to reference the correct
+  // members. If the member being inserted is not live, then |inst| is killed.
+  // This function must be called after the |OpTypeStruct| instruction for the
+  // type has been updated.  Return true if something changed.
+  bool UpdateCompositeInsert(Instruction* inst);
+
+  // Update the |OpArrayLength| instruction |inst| to reference the correct
+  // member. The member referenced in the instruction must be live.  Return true
+  // if something changed.
+  bool UpdateOpArrayLength(Instruction* inst);
+
+  // Add all of the members of type |type_id| and members of any subtypes to
+  // |used_members_|.
+  void MarkTypeAsFullyUsed(uint32_t type_id);
+
+  // Add all of the members of the type of the operand |in_idx| in |inst| and
+  // members of any subtypes to |uses_members_|.
+  void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx);
+
+  // Return the index of the member that use to be the |member_idx|th member of
+  // |type_id|.  If the member has been removed, |kRemovedMember| is returned.
+  uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx);
+
+  // A map from a type id to a set of indices representing the members of the
+  // type that are used, and must be kept.
+  std::unordered_map<uint32_t, std::set<uint32_t>> used_members_;
+  void MarkStructOperandsAsFullyUsed(const Instruction* inst);
+  void MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
diff --git a/source/opt/fix_storage_class.cpp b/source/opt/fix_storage_class.cpp
new file mode 100644
index 0000000..03da0d0
--- /dev/null
+++ b/source/opt/fix_storage_class.cpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fix_storage_class.h"
+
+#include <set>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status FixStorageClass::Process() {
+  bool modified = false;
+
+  get_module()->ForEachInst([this, &modified](Instruction* inst) {
+    if (inst->opcode() == SpvOpVariable) {
+      std::set<uint32_t> seen;
+      std::vector<std::pair<Instruction*, uint32_t>> uses;
+      get_def_use_mgr()->ForEachUse(inst,
+                                    [&uses](Instruction* use, uint32_t op_idx) {
+                                      uses.push_back({use, op_idx});
+                                    });
+
+      for (auto& use : uses) {
+        modified |= PropagateStorageClass(
+            use.first,
+            static_cast<SpvStorageClass>(inst->GetSingleWordInOperand(0)),
+            &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+        modified |=
+            PropagateType(use.first, inst->type_id(), use.second, &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+      }
+    }
+  });
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool FixStorageClass::PropagateStorageClass(Instruction* inst,
+                                            SpvStorageClass storage_class,
+                                            std::set<uint32_t>* seen) {
+  if (!IsPointerResultType(inst)) {
+    return false;
+  }
+
+  if (IsPointerToStorageClass(inst, storage_class)) {
+    if (inst->opcode() == SpvOpPhi) {
+      if (!seen->insert(inst->result_id()).second) {
+        return false;
+      }
+    }
+
+    bool modified = false;
+    std::vector<Instruction*> uses;
+    get_def_use_mgr()->ForEachUser(
+        inst, [&uses](Instruction* use) { uses.push_back(use); });
+    for (Instruction* use : uses) {
+      modified |= PropagateStorageClass(use, storage_class, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+    return modified;
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpCopyObject:
+    case SpvOpPhi:
+    case SpvOpSelect:
+      FixInstructionStorageClass(inst, storage_class, seen);
+      return true;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the storage class
+      // of the parameter and the storage class of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpImageTexelPointer:
+    case SpvOpLoad:
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+    case SpvOpVariable:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the storage class of the operand.
+      return false;
+    default:
+      assert(false &&
+             "Not expecting instruction to have a pointer result type.");
+      return false;
+  }
+}
+
+void FixStorageClass::FixInstructionStorageClass(Instruction* inst,
+                                                 SpvStorageClass storage_class,
+                                                 std::set<uint32_t>* seen) {
+  assert(IsPointerResultType(inst) &&
+         "The result type of the instruction must be a pointer.");
+
+  ChangeResultStorageClass(inst, storage_class);
+
+  std::vector<Instruction*> uses;
+  get_def_use_mgr()->ForEachUser(
+      inst, [&uses](Instruction* use) { uses.push_back(use); });
+  for (Instruction* use : uses) {
+    PropagateStorageClass(use, storage_class, seen);
+  }
+}
+
+void FixStorageClass::ChangeResultStorageClass(
+    Instruction* inst, SpvStorageClass storage_class) const {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
+  assert(result_type_inst->opcode() == SpvOpTypePointer);
+  uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1);
+  uint32_t new_result_type_id =
+      type_mgr->FindPointerToType(pointee_type_id, storage_class);
+  inst->SetResultType(new_result_type_id);
+  context()->UpdateDefUse(inst);
+}
+
+bool FixStorageClass::IsPointerResultType(Instruction* inst) {
+  if (inst->type_id() == 0) {
+    return false;
+  }
+  const analysis::Type* ret_type =
+      context()->get_type_mgr()->GetType(inst->type_id());
+  return ret_type->AsPointer() != nullptr;
+}
+
+bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
+                                              SpvStorageClass storage_class) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Type* pType = type_mgr->GetType(inst->type_id());
+  const analysis::Pointer* result_type = pType->AsPointer();
+
+  if (result_type == nullptr) {
+    return false;
+  }
+
+  return (result_type->storage_class() == storage_class);
+}
+
+bool FixStorageClass::ChangeResultType(Instruction* inst,
+                                       uint32_t new_type_id) {
+  if (inst->type_id() == new_type_id) {
+    return false;
+  }
+
+  context()->ForgetUses(inst);
+  inst->SetResultType(new_type_id);
+  context()->AnalyzeUses(inst);
+  return true;
+}
+
+bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
+                                    uint32_t op_idx, std::set<uint32_t>* seen) {
+  assert(type_id != 0 && "Not given a valid type in PropagateType");
+  bool modified = false;
+
+  // If the type of operand |op_idx| forces the result type of |inst| to a
+  // particular type, then we want find that type.
+  uint32_t new_type_id = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      if (op_idx == 2) {
+        new_type_id = WalkAccessChainType(inst, type_id);
+      }
+      break;
+    case SpvOpCopyObject:
+      new_type_id = type_id;
+      break;
+    case SpvOpPhi:
+      if (seen->insert(inst->result_id()).second) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpSelect:
+      if (op_idx > 2) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the type
+      // of the parameter and the type of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpLoad: {
+      Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+      new_type_id = type_inst->GetSingleWordInOperand(1);
+      break;
+    }
+    case SpvOpStore: {
+      uint32_t obj_id = inst->GetSingleWordInOperand(1);
+      Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id);
+      uint32_t obj_type_id = obj_inst->type_id();
+
+      uint32_t ptr_id = inst->GetSingleWordInOperand(0);
+      Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+      uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
+
+      if (obj_type_id != pointee_type_id) {
+        uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
+        inst->SetInOperand(1, {copy_id});
+        context()->UpdateDefUse(inst);
+      }
+    } break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      // TODO: May need to expand the copy as we do with the stores.
+      break;
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+      // TODO: DXC does not seem to generate code that will require changes to
+      // these opcode.  The can be implemented when they come up.
+      break;
+    case SpvOpImageTexelPointer:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the type of the operand.
+      return false;
+    default:
+      // I expect the remaining instructions to act on types that are guaranteed
+      // to be unique, so no change will be necessary.
+      break;
+  }
+
+  // If the operand forces the result type, then make sure the result type
+  // matches, and update the uses of |inst|.  We do not have to check the uses
+  // of |inst| in the result type is not forced because we are only looking for
+  // issue that come from mismatches between function formal and actual
+  // parameters after the function has been inlined.  These parameters are
+  // pointers. Once the type no longer depends on the type of the parameter,
+  // then the types should have be correct.
+  if (new_type_id != 0) {
+    modified = ChangeResultType(inst, new_type_id);
+
+    std::vector<std::pair<Instruction*, uint32_t>> uses;
+    get_def_use_mgr()->ForEachUse(inst,
+                                  [&uses](Instruction* use, uint32_t idx) {
+                                    uses.push_back({use, idx});
+                                  });
+
+    for (auto& use : uses) {
+      PropagateType(use.first, new_type_id, use.second, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+  }
+  return modified;
+}
+
+uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
+  uint32_t start_idx = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+      start_idx = 1;
+      break;
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      start_idx = 2;
+      break;
+    default:
+      assert(false);
+      break;
+  }
+
+  Instruction* orig_type_inst = get_def_use_mgr()->GetDef(id);
+  assert(orig_type_inst->opcode() == SpvOpTypePointer);
+  id = orig_type_inst->GetSingleWordInOperand(1);
+
+  for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+        id = type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeStruct: {
+        const analysis::Constant* index_const =
+            context()->get_constant_mgr()->FindDeclaredConstant(
+                inst->GetSingleWordInOperand(i));
+        uint32_t index = index_const->GetU32();
+        id = type_inst->GetSingleWordInOperand(index);
+        break;
+      }
+      default:
+        break;
+    }
+    assert(id != 0 &&
+           "Tried to extract from an object where it cannot be done.");
+  }
+
+  return context()->get_type_mgr()->FindPointerToType(
+      id,
+      static_cast<SpvStorageClass>(orig_type_inst->GetSingleWordInOperand(0)));
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/fix_storage_class.h b/source/opt/fix_storage_class.h
new file mode 100644
index 0000000..e72e864
--- /dev/null
+++ b/source/opt/fix_storage_class.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_FIX_STORAGE_CLASS_H_
+#define SOURCE_OPT_FIX_STORAGE_CLASS_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass tries to fix validation error due to a mismatch of storage classes
+// in instructions.  There is no guarantee that all such error will be fixed,
+// and it is possible that in fixing these errors, it could lead to other
+// errors.
+class FixStorageClass : public Pass {
+ public:
+  const char* name() const override { return "fix-storage-class"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Changes the storage class of the result of |inst| to |storage_class| in
+  // appropriate, and propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed.
+  bool PropagateStorageClass(Instruction* inst, SpvStorageClass storage_class,
+                             std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.
+  // Is it assumed that the result type of |inst| is a pointer type.
+  // Propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed by
+  // |PropagateStorageClass|
+  void FixInstructionStorageClass(Instruction* inst,
+                                  SpvStorageClass storage_class,
+                                  std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.  The
+  // result type of |inst| must be a pointer.
+  void ChangeResultStorageClass(Instruction* inst,
+                                SpvStorageClass storage_class) const;
+
+  // Returns true if the result type of |inst| is a pointer.
+  bool IsPointerResultType(Instruction* inst);
+
+  // Returns true if the result of |inst| is a pointer to storage class
+  // |storage_class|.
+  bool IsPointerToStorageClass(Instruction* inst,
+                               SpvStorageClass storage_class);
+
+  // Change |inst| to match that operand |op_idx| now has type |type_id|, and
+  // adjust any uses of |inst| accordingly. Returns true if the code changed.
+  bool PropagateType(Instruction* inst, uint32_t type_id, uint32_t op_idx,
+                     std::set<uint32_t>* seen);
+
+  // Changes the result type of |inst| to |new_type_id|.
+  bool ChangeResultType(Instruction* inst, uint32_t new_type_id);
+
+  // Returns the type id of the member of the type |id| that would be returned
+  // by following the indices of the access chain instruction |inst|.
+  uint32_t WalkAccessChainType(Instruction* inst, uint32_t id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_FIX_STORAGE_CLASS_H_
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
index 663d112..56d0137 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.cpp
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
@@ -120,19 +120,15 @@
 
   switch (static_cast<SpvOp>(inst->GetSingleWordInOperand(0))) {
     case SpvOp::SpvOpCompositeExtract:
-      folded_inst = DoCompositeExtract(pos);
-      break;
     case SpvOp::SpvOpVectorShuffle:
-      folded_inst = DoVectorShuffle(pos);
-      break;
-
     case SpvOp::SpvOpCompositeInsert:
-      // Current Glslang does not generate code with OpSpecConstantOp
-      // CompositeInsert instruction, so this is not implmented so far.
-      // TODO(qining): Implement CompositeInsert case.
-      return false;
-
+    case SpvOp::SpvOpQuantizeToF16:
+      folded_inst = FoldWithInstructionFolder(pos);
+      break;
     default:
+      // TODO: This should use the instruction folder as well, but some folding
+      // rules are missing.
+
       // Component-wise operations.
       folded_inst = DoComponentWiseOperation(pos);
       break;
@@ -157,54 +153,65 @@
   return subtype;
 }
 
-Instruction* FoldSpecConstantOpAndCompositePass::DoCompositeExtract(
-    Module::inst_iterator* pos) {
-  Instruction* inst = &**pos;
-  assert(inst->NumInOperands() - 1 >= 2 &&
-         "OpSpecConstantOp CompositeExtract requires at least two non-type "
-         "non-opcode operands.");
-  assert(inst->GetInOperand(1).type == SPV_OPERAND_TYPE_ID &&
-         "The composite operand must have a SPV_OPERAND_TYPE_ID type");
-  assert(
-      inst->GetInOperand(2).type == SPV_OPERAND_TYPE_LITERAL_INTEGER &&
-      "The literal operand must have a SPV_OPERAND_TYPE_LITERAL_INTEGER type");
-
-  // Note that for OpSpecConstantOp, the second in-operand is the first id
-  // operand. The first in-operand is the spec opcode.
-  uint32_t source = inst->GetSingleWordInOperand(1);
-  uint32_t type = context()->get_def_use_mgr()->GetDef(source)->type_id();
-  const analysis::Constant* first_operand_const =
-      context()->get_constant_mgr()->FindDeclaredConstant(source);
-  if (!first_operand_const) return nullptr;
-
-  const analysis::Constant* current_const = first_operand_const;
-  for (uint32_t i = 2; i < inst->NumInOperands(); i++) {
-    uint32_t literal = inst->GetSingleWordInOperand(i);
-    type = GetTypeComponent(type, literal);
-  }
-  for (uint32_t i = 2; i < inst->NumInOperands(); i++) {
-    uint32_t literal = inst->GetSingleWordInOperand(i);
-    if (const analysis::CompositeConstant* composite_const =
-            current_const->AsCompositeConstant()) {
-      // Case 1: current constant is a non-null composite type constant.
-      assert(literal < composite_const->GetComponents().size() &&
-             "Literal index out of bound of the composite constant");
-      current_const = composite_const->GetComponents().at(literal);
-    } else if (current_const->AsNullConstant()) {
-      // Case 2: current constant is a constant created with OpConstantNull.
-      // Because components of a NullConstant are always NullConstants, we can
-      // return early with a NullConstant in the result type.
-      return context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-          context()->get_constant_mgr()->GetConstant(
-              context()->get_constant_mgr()->GetType(inst), {}),
-          pos, type);
-    } else {
-      // Dereferencing a non-composite constant. Invalid case.
+Instruction* FoldSpecConstantOpAndCompositePass::FoldWithInstructionFolder(
+    Module::inst_iterator* inst_iter_ptr) {
+  // 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++) {
+    const Operand& operand = (*inst_iter_ptr)->GetInOperand(i);
+    if (operand.type != SPV_OPERAND_TYPE_ID &&
+        operand.type != SPV_OPERAND_TYPE_OPTIONAL_ID) {
+      continue;
+    }
+    uint32_t id = operand.words[0];
+    if (context()->get_constant_mgr()->FindDeclaredConstant(id) == nullptr) {
       return nullptr;
     }
   }
-  return context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-      current_const, pos);
+
+  // All of the operands are constant.  Construct a regular version of the
+  // instruction and pass it to the instruction folder.
+  std::unique_ptr<Instruction> inst((*inst_iter_ptr)->Clone(context()));
+  inst->SetOpcode(
+      static_cast<SpvOp>((*inst_iter_ptr)->GetSingleWordInOperand(0)));
+  inst->RemoveOperand(2);
+
+  // We want the current instruction to be replaced by an |OpConstant*|
+  // instruction in the same position. We need to keep track of which constants
+  // the instruction folder creates, so we can move them into the correct place.
+  auto last_type_value_iter = (context()->types_values_end());
+  --last_type_value_iter;
+  Instruction* last_type_value = &*last_type_value_iter;
+
+  auto identity_map = [](uint32_t id) { return id; };
+  Instruction* new_const_inst =
+      context()->get_instruction_folder().FoldInstructionToConstant(
+          inst.get(), identity_map);
+  assert(new_const_inst != nullptr &&
+         "Failed to fold instruction that must be folded.");
+
+  // Get the instruction before |pos| to insert after.  |pos| cannot be the
+  // first instruction in the list because its type has to come first.
+  Instruction* insert_pos = (*inst_iter_ptr)->PreviousNode();
+  assert(insert_pos != nullptr &&
+         "pos is the first instruction in the types and values.");
+  bool need_to_clone = true;
+  for (Instruction* i = last_type_value->NextNode(); i != nullptr;
+       i = last_type_value->NextNode()) {
+    if (i == new_const_inst) {
+      need_to_clone = false;
+    }
+    i->InsertAfter(insert_pos);
+    insert_pos = insert_pos->NextNode();
+  }
+
+  if (need_to_clone) {
+    new_const_inst = new_const_inst->Clone(context());
+    new_const_inst->SetResultId(TakeNextId());
+    new_const_inst->InsertAfter(insert_pos);
+    get_def_use_mgr()->AnalyzeInstDefUse(new_const_inst);
+  }
+  return new_const_inst;
 }
 
 Instruction* FoldSpecConstantOpAndCompositePass::DoVectorShuffle(
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 1627125..361d3ca 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.h
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.h
@@ -54,11 +54,9 @@
   // it.
   bool ProcessOpSpecConstantOp(Module::inst_iterator* pos);
 
-  // Try to fold the OpSpecConstantOp CompositeExtract 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 returns nullptr.
-  Instruction* DoCompositeExtract(Module::inst_iterator* inst_iter_ptr);
+  // Returns the result of folding the OpSpecConstantOp instruction
+  // |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.
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 3274319..18d5149 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -2167,6 +2167,37 @@
   };
 }
 
+// Removes duplicate ids from the interface list of an OpEntryPoint
+// instruction.
+FoldingRule RemoveRedundantOperands() {
+  return [](IRContext*, Instruction* inst,
+            const std::vector<const analysis::Constant*>&) {
+    assert(inst->opcode() == SpvOpEntryPoint &&
+           "Wrong opcode.  Should be OpEntryPoint.");
+    bool has_redundant_operand = false;
+    std::unordered_set<uint32_t> seen_operands;
+    std::vector<Operand> new_operands;
+
+    new_operands.emplace_back(inst->GetOperand(0));
+    new_operands.emplace_back(inst->GetOperand(1));
+    new_operands.emplace_back(inst->GetOperand(2));
+    for (uint32_t i = 3; i < inst->NumOperands(); ++i) {
+      if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) {
+        new_operands.emplace_back(inst->GetOperand(i));
+      } else {
+        has_redundant_operand = true;
+      }
+    }
+
+    if (!has_redundant_operand) {
+      return false;
+    }
+
+    inst->SetInOperands(std::move(new_operands));
+    return true;
+  };
+}
+
 }  // namespace
 
 FoldingRules::FoldingRules() {
@@ -2183,6 +2214,8 @@
 
   rules_[SpvOpDot].push_back(DotProductDoingExtract());
 
+  rules_[SpvOpEntryPoint].push_back(RemoveRedundantOperands());
+
   rules_[SpvOpExtInst].push_back(RedundantFMix());
 
   rules_[SpvOpFAdd].push_back(RedundantFAdd());
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index 9bd46e2..2520052 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -147,6 +147,19 @@
   return nullptr;
 }
 
+BasicBlock* Function::InsertBasicBlockBefore(
+    std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position) {
+  for (auto bb_iter = begin(); bb_iter != end(); ++bb_iter) {
+    if (&*bb_iter == position) {
+      new_block->SetParent(this);
+      bb_iter = bb_iter.InsertBefore(std::move(new_block));
+      return &*bb_iter;
+    }
+  }
+  assert(false && "Could not find insertion point.");
+  return nullptr;
+}
+
 bool Function::IsRecursive() const {
   IRContext* ctx = blocks_.front()->GetLabel()->context();
   IRContext::ProcessFunction mark_visited = [this](Function* fp) {
diff --git a/source/opt/function.h b/source/opt/function.h
index c80b078..b1317ad 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -125,6 +125,9 @@
   BasicBlock* InsertBasicBlockAfter(std::unique_ptr<BasicBlock>&& new_block,
                                     BasicBlock* position);
 
+  BasicBlock* InsertBasicBlockBefore(std::unique_ptr<BasicBlock>&& new_block,
+                                     BasicBlock* position);
+
   // Return true if the function calls itself either directly or indirectly.
   bool IsRecursive() const;
 
diff --git a/source/opt/generate_webgpu_initializers_pass.cpp b/source/opt/generate_webgpu_initializers_pass.cpp
new file mode 100644
index 0000000..9334b43
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/generate_webgpu_initializers_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool NeedsWebGPUInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+
+  auto storage_class = inst->GetSingleWordOperand(2);
+  if (storage_class != SpvStorageClassOutput &&
+      storage_class != SpvStorageClassPrivate &&
+      storage_class != SpvStorageClassFunction) {
+    return false;
+  }
+
+  if (inst->NumOperands() > 3) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status GenerateWebGPUInitializersPass::Process() {
+  auto* module = context()->module();
+  bool changed = false;
+
+  // Handle global/module scoped variables
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+
+    if (inst->opcode() == SpvOpConstantNull) {
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+      continue;
+    }
+
+    if (!NeedsWebGPUInitializer(inst)) continue;
+
+    changed = true;
+
+    auto* constant_inst = GetNullConstantForVariable(inst);
+    if (seen_null_constants_.find(constant_inst) ==
+        seen_null_constants_.end()) {
+      constant_inst->InsertBefore(inst);
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+    }
+    AddNullInitializerToVariable(constant_inst, inst);
+  }
+
+  // Handle local/function scoped variables
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    auto block = func->entry().get();
+    for (auto iter = block->begin();
+         iter != block->end() && iter->opcode() == SpvOpVariable; ++iter) {
+      Instruction* inst = &(*iter);
+      if (!NeedsWebGPUInitializer(inst)) continue;
+
+      changed = true;
+      auto* constant_inst = GetNullConstantForVariable(inst);
+      AddNullInitializerToVariable(constant_inst, inst);
+    }
+  }
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Instruction* GenerateWebGPUInitializersPass::GetNullConstantForVariable(
+    Instruction* variable_inst) {
+  auto constant_mgr = context()->get_constant_mgr();
+  auto* def_use_mgr = get_def_use_mgr();
+
+  auto* ptr_inst = def_use_mgr->GetDef(variable_inst->type_id());
+  auto type_id = ptr_inst->GetInOperand(1).words[0];
+  if (null_constant_type_map_.find(type_id) == null_constant_type_map_.end()) {
+    auto* constant_type = context()->get_type_mgr()->GetType(type_id);
+    auto* constant = constant_mgr->GetConstant(constant_type, {});
+    return constant_mgr->GetDefiningInstruction(constant, type_id);
+  } else {
+    return null_constant_type_map_[type_id];
+  }
+}
+
+void GenerateWebGPUInitializersPass::AddNullInitializerToVariable(
+    Instruction* constant_inst, Instruction* variable_inst) {
+  auto constant_id = constant_inst->result_id();
+  variable_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {constant_id}));
+  get_def_use_mgr()->AnalyzeInstUse(variable_inst);
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/generate_webgpu_initializers_pass.h b/source/opt/generate_webgpu_initializers_pass.h
new file mode 100644
index 0000000..f95e84c
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+#define SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Adds initializers to variables with storage classes Output, Private, and
+// Function if they are missing. In the WebGPU environment these storage classes
+// require that the variables are initialized. Currently they are initialized to
+// NULL, though in the future some of them may be initialized to the first value
+// that is stored in them, if that was a constant.
+class GenerateWebGPUInitializersPass : public Pass {
+ public:
+  const char* name() const override { return "generate-webgpu-initializers"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+
+ private:
+  using NullConstantTypeMap = std::unordered_map<uint32_t, Instruction*>;
+  NullConstantTypeMap null_constant_type_map_;
+  std::unordered_set<Instruction*> seen_null_constants_;
+
+  Instruction* GetNullConstantForVariable(Instruction* variable_inst);
+  void AddNullInitializerToVariable(Instruction* constant_inst,
+                                    Instruction* variable_inst);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 1901f76..b283354 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -29,20 +29,94 @@
 static const int kSpvTypePointerTypeIdInIdx = 1;
 static const int kSpvTypeArrayLengthIdInIdx = 1;
 static const int kSpvConstantValueInIdx = 0;
+static const int kSpvVariableStorageClassInIdx = 0;
 
 }  // anonymous namespace
 
 namespace spvtools {
 namespace opt {
 
-void InstBindlessCheckPass::GenBindlessCheckCode(
-    BasicBlock::iterator ref_inst_itr,
-    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-    uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-  // Look for reference through bindless descriptor. If not, return.
-  std::unique_ptr<BasicBlock> new_blk_ptr;
-  uint32_t image_id;
-  switch (ref_inst_itr->opcode()) {
+uint32_t InstBindlessCheckPass::GenDebugReadLength(
+    uint32_t var_id, InstructionBuilder* builder) {
+  uint32_t desc_set_idx =
+      var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
+}
+
+uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
+                                                 uint32_t desc_idx_id,
+                                                 InstructionBuilder* builder) {
+  uint32_t desc_set_base_id =
+      builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
+  return GenDebugDirectRead(
+      {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+      builder);
+}
+
+uint32_t InstBindlessCheckPass::CloneOriginalReference(
+    ref_analysis* ref, InstructionBuilder* builder) {
+  // If original is image based, start by cloning descriptor load
+  uint32_t new_image_id = 0;
+  if (ref->desc_load_id != 0) {
+    Instruction* desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
+    Instruction* new_load_inst = builder->AddLoad(
+        desc_load_inst->type_id(),
+        desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
+    uid2offset_[new_load_inst->unique_id()] =
+        uid2offset_[desc_load_inst->unique_id()];
+    uint32_t new_load_id = new_load_inst->result_id();
+    get_decoration_mgr()->CloneDecorations(desc_load_inst->result_id(),
+                                           new_load_id);
+    new_image_id = new_load_id;
+    // Clone Image/SampledImage with new load, if needed
+    if (ref->image_id != 0) {
+      Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
+      if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+        Instruction* new_image_inst = builder->AddBinaryOp(
+            image_inst->type_id(), SpvOpSampledImage, new_load_id,
+            image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
+        uid2offset_[new_image_inst->unique_id()] =
+            uid2offset_[image_inst->unique_id()];
+        new_image_id = new_image_inst->result_id();
+      } else {
+        assert(image_inst->opcode() == SpvOp::SpvOpImage &&
+               "expecting OpImage");
+        Instruction* new_image_inst =
+            builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
+        uid2offset_[new_image_inst->unique_id()] =
+            uid2offset_[image_inst->unique_id()];
+        new_image_id = new_image_inst->result_id();
+      }
+      get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
+    }
+  }
+  // Clone original reference
+  std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
+  uint32_t ref_result_id = ref->ref_inst->result_id();
+  uint32_t new_ref_id = 0;
+  if (ref_result_id != 0) {
+    new_ref_id = TakeNextId();
+    new_ref_inst->SetResultId(new_ref_id);
+  }
+  // Update new ref with new image if created
+  if (new_image_id != 0)
+    new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
+  // Register new reference and add to new block
+  Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
+  uid2offset_[added_inst->unique_id()] =
+      uid2offset_[ref->ref_inst->unique_id()];
+  if (new_ref_id != 0)
+    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
+  return new_ref_id;
+}
+
+uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
+  switch (inst->opcode()) {
     case SpvOp::SpvOpImageSampleImplicitLod:
     case SpvOp::SpvOpImageSampleExplicitLod:
     case SpvOp::SpvOpImageSampleDrefImplicitLod:
@@ -75,182 +149,291 @@
     case SpvOp::SpvOpImageSparseFetch:
     case SpvOp::SpvOpImageSparseRead:
     case SpvOp::SpvOpImageWrite:
-      image_id =
-          ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
-      break;
+      return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
     default:
-      return;
+      break;
   }
-  Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
-  uint32_t load_id;
-  Instruction* load_inst;
-  if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
-  } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
-  } else {
-    load_id = image_id;
-    load_inst = image_inst;
-    image_id = 0;
-  }
-  if (load_inst->opcode() != SpvOp::SpvOpLoad) {
-    // TODO(greg-lunarg): Handle additional possibilities
-    return;
-  }
-  uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
-  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
-  if (ptr_inst->NumInOperands() != 2) {
-    assert(false && "unexpected bindless index number");
-    return;
-  }
-  uint32_t index_id =
-      ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
-  ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
-  ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOpVariable) {
-    assert(false && "unexpected bindless base");
-    return;
-  }
-  uint32_t var_type_id = ptr_inst->type_id();
+  return 0;
+}
+
+Instruction* InstBindlessCheckPass::GetDescriptorTypeInst(
+    Instruction* var_inst) {
+  uint32_t var_type_id = var_inst->type_id();
   Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
-  uint32_t ptr_type_id =
+  uint32_t desc_type_id =
       var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
-  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-  // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
-  // out of debug input buffer.
-  if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
-  // If index and bound both compile-time constants and index < bound,
-  // return without changing
-  uint32_t length_id =
-      ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-  Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
-  Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
-  if (index_inst->opcode() == SpvOpConstant &&
-      length_inst->opcode() == SpvOpConstant &&
-      index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
-          length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
-    return;
-  // Generate full runtime bounds test code with true branch
-  // being full reference and false branch being debug output and zero
-  // for the referenced value.
-  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  return get_def_use_mgr()->GetDef(desc_type_id);
+}
+
+bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
+                                                       ref_analysis* ref) {
+  ref->ref_inst = ref_inst;
+  if (ref_inst->opcode() == SpvOpLoad || ref_inst->opcode() == SpvOpStore) {
+    ref->desc_load_id = 0;
+    ref->ptr_id = ref_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+    Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+    if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return false;
+    ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+    Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+    if (var_inst->opcode() != SpvOp::SpvOpVariable) return false;
+    uint32_t storage_class =
+        var_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx);
+    switch (storage_class) {
+      case SpvStorageClassUniform:
+      case SpvStorageClassUniformConstant:
+      case SpvStorageClassStorageBuffer:
+        break;
+      default:
+        return false;
+        break;
+    }
+    Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+    switch (desc_type_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+        // A load through a descriptor array will have at least 3 operands. We
+        // do not want to instrument loads of descriptors here which are part of
+        // an image-based reference.
+        if (ptr_inst->NumInOperands() < 3) return false;
+        ref->index_id =
+            ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+        break;
+      default:
+        ref->index_id = 0;
+        break;
+    }
+    return true;
+  }
+  // Reference is not load or store. If not an image-based reference, return.
+  ref->image_id = GetImageId(ref_inst);
+  if (ref->image_id == 0) return false;
+  Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
+  Instruction* desc_load_inst = nullptr;
+  if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+    ref->desc_load_id =
+        image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
+    desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
+  } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
+    ref->desc_load_id =
+        image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
+    desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
+  } else {
+    ref->desc_load_id = ref->image_id;
+    desc_load_inst = image_inst;
+    ref->image_id = 0;
+  }
+  if (desc_load_inst->opcode() != SpvOp::SpvOpLoad) {
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
+  }
+  ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+  if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
+    ref->index_id = 0;
+    ref->var_id = ref->ptr_id;
+  } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
+    if (ptr_inst->NumInOperands() != 2) {
+      assert(false && "unexpected bindless index number");
+      return false;
+    }
+    ref->index_id =
+        ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+    ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+    Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+    if (var_inst->opcode() != SpvOpVariable) {
+      assert(false && "unexpected bindless base");
+      return false;
+    }
+  } else {
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
+  }
+  return true;
+}
+
+void InstBindlessCheckPass::GenCheckCode(
+    uint32_t check_id, uint32_t error_id, uint32_t length_id,
+    uint32_t stage_idx, ref_analysis* ref,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
   InstructionBuilder builder(
-      context(), &*new_blk_ptr,
+      context(), back_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
-  Instruction* ult_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+  // Gen conditional branch on check_id. Valid branch generates original
+  // reference. Invalid generates debug output and zero result (if needed).
   uint32_t merge_blk_id = TakeNextId();
   uint32_t valid_blk_id = TakeNextId();
   uint32_t invalid_blk_id = TakeNextId();
   std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
   std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
   std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
-  (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
-                                     invalid_blk_id, merge_blk_id,
-                                     SpvSelectionControlMaskNone);
-  // Close selection block and gen valid reference block
-  new_blocks->push_back(std::move(new_blk_ptr));
-  new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
+  (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
+                                     merge_blk_id, SpvSelectionControlMaskNone);
+  // Gen valid bounds branch
+  std::unique_ptr<BasicBlock> new_blk_ptr(
+      new BasicBlock(std::move(valid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  // Clone descriptor load
-  Instruction* new_load_inst =
-      builder.AddLoad(load_inst->type_id(),
-                      load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
-  uint32_t new_load_id = new_load_inst->result_id();
-  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
-  uint32_t new_image_id = new_load_id;
-  // Clone Image/SampledImage with new load, if needed
-  if (image_id != 0) {
-    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-      Instruction* new_image_inst = builder.AddBinaryOp(
-          image_inst->type_id(), SpvOpSampledImage, new_load_id,
-          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
-      new_image_id = new_image_inst->result_id();
-    } else {
-      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
-      Instruction* new_image_inst =
-          builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
-      new_image_id = new_image_inst->result_id();
-    }
-    get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
-  }
-  // Clone original reference using new image code
-  std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
-  uint32_t ref_result_id = ref_inst_itr->result_id();
-  uint32_t new_ref_id = 0;
-  if (ref_result_id != 0) {
-    new_ref_id = TakeNextId();
-    new_ref_inst->SetResultId(new_ref_id);
-  }
-  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
-  // Register new reference and add to new block
-  builder.AddInstruction(std::move(new_ref_inst));
-  if (new_ref_id != 0)
-    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
-  // Close valid block and gen invalid block
+  uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen invalid block
   new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  uint32_t u_index_id = GenUintCastCode(index_id, &builder);
-  GenDebugStreamWrite(instruction_idx, stage_idx,
+  uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
+  GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
                       {error_id, u_index_id, length_id}, &builder);
   // Remember last invalid block id
   uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
   // Gen zero for invalid  reference
-  uint32_t ref_type_id = ref_inst_itr->type_id();
-  // Close invalid block and gen merge block
+  uint32_t ref_type_id = ref->ref_inst->type_id();
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen merge block
   new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
   // Gen phi of new reference and zero, if necessary, and replace the
   // result id of the original reference with that of the Phi. Kill original
-  // reference and move in remainder of original block.
+  // reference.
   if (new_ref_id != 0) {
     Instruction* phi_inst = builder.AddPhi(
         ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
                       last_invalid_blk_id});
-    context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
+    context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
+                                  phi_inst->result_id());
   }
-  context()->KillInst(&*ref_inst_itr);
-  MovePostludeCode(ref_block_itr, &new_blk_ptr);
-  // Add remainder/merge block to new blocks
   new_blocks->push_back(std::move(new_blk_ptr));
+  context()->KillInst(ref->ref_inst);
+}
+
+void InstBindlessCheckPass::GenBoundsCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through indexed descriptor. If found, analyze and
+  // save components. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
+  // If index and bound both compile-time constants and index < bound,
+  // return without changing
+  Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
+  Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+  uint32_t length_id = 0;
+  if (desc_type_inst->opcode() == SpvOpTypeArray) {
+    length_id =
+        desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+    Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+    if (index_inst->opcode() == SpvOpConstant &&
+        length_inst->opcode() == SpvOpConstant &&
+        index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+            length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+      return;
+  } else if (!input_length_enabled_ ||
+             desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
+    return;
+  }
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+  // If length id not yet set, descriptor array is runtime size so
+  // generate load of length from stage's debug input buffer.
+  if (length_id == 0) {
+    assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
+           "unexpected bindless type");
+    length_id = GenDebugReadLength(ref.var_id, &builder);
+  }
+  // Generate full runtime bounds test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
+  GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
+}
+
+void InstBindlessCheckPass::GenInitCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through descriptor. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  // Read initialization status from debug input buffer. If index id not yet
+  // set, binding is single descriptor, so set index to constant 0.
+  uint32_t zero_id = builder.GetUintConstantId(0u);
+  if (ref.index_id == 0) ref.index_id = zero_id;
+  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
+  // Generate full runtime non-zero init test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* uneq_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
+  GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
 }
 
 void InstBindlessCheckPass::InitializeInstBindlessCheck() {
   // Initialize base class
   InitializeInstrument();
-  // Look for related extensions
-  ext_descriptor_indexing_defined_ = false;
-  for (auto& ei : get_module()->extensions()) {
-    const char* ext_name =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
-    if (strcmp(ext_name, "SPV_EXT_descriptor_indexing") == 0) {
-      ext_descriptor_indexing_defined_ = true;
-      break;
-    }
-  }
+  // If runtime array length support enabled, create variable mappings. Length
+  // support is always enabled if descriptor init check is enabled.
+  if (input_length_enabled_)
+    for (auto& anno : get_module()->annotations())
+      if (anno.opcode() == SpvOpDecorate) {
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
+          var2desc_set_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+        else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
+          var2binding_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+      }
 }
 
 Pass::Status InstBindlessCheckPass::ProcessImpl() {
-  // Perform instrumentation on each entry point function in module
+  // Perform bindless bounds check on each entry point function in module
   InstProcessFunction pfn =
       [this](BasicBlock::iterator ref_inst_itr,
-             UptrVectorIterator<BasicBlock> ref_block_itr,
-             uint32_t instruction_idx, uint32_t stage_idx,
+             UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
              std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-        return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
-                                    instruction_idx, stage_idx, new_blocks);
+        return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
       };
   bool modified = InstProcessEntryPointCallTree(pfn);
-  // This pass does not update inst->blk info
-  context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+  if (input_init_enabled_) {
+    // Perform descriptor initialization check on each entry point function in
+    // module
+    pfn = [this](BasicBlock::iterator ref_inst_itr,
+                 UptrVectorIterator<BasicBlock> ref_block_itr,
+                 uint32_t stage_idx,
+                 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+      return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                              new_blocks);
+    };
+    modified |= InstProcessEntryPointCallTree(pfn);
+  }
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index 3ab5ab7..51d7712 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -29,10 +29,17 @@
 class InstBindlessCheckPass : public InstrumentPass {
  public:
   // For test harness only
-  InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+  InstBindlessCheckPass()
+      : InstrumentPass(7, 23, kInstValidationIdBindless, 1),
+        input_length_enabled_(true),
+        input_init_enabled_(true) {}
   // For all other interfaces
-  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool input_length_enable, bool input_init_enable,
+                        uint32_t version)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, version),
+        input_length_enabled_(input_length_enable),
+        input_init_enabled_(input_init_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -42,28 +49,40 @@
   const char* name() const override { return "inst-bindless-check-pass"; }
 
  private:
-  // Initialize state for instrumenting bindless checking
-  void InitializeInstBindlessCheck();
-
-  // This function does bindless checking instrumentation on a single
-  // instruction. It is designed to be passed to
+  // These functions do bindless checking instrumentation on a single
+  // instruction which references through a descriptor (ie references into an
+  // image or buffer). Refer to Vulkan API for further information on
+  // descriptors. GenBoundsCheckCode checks that an index into a descriptor
+  // array (array of images or buffers) is in-bounds. GenInitCheckCode
+  // checks that the referenced descriptor has been initialized, if the
+  // SPV_EXT_descriptor_indexing extension is enabled.
+  //
+  // TODO(greg-lunarg): Add support for buffers. Currently only does
+  // checking of references of images.
+  //
+  // The functions are designed to be passed to
   // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
   // function to each instruction in a module and replaces the instruction
   // if warranted.
   //
   // If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
   // result of instrumenting it with validation code within its block at
-  // |ref_block_itr|. Specifically, generate code to check that the index
-  // into the descriptor array is in-bounds. If the check passes, execute
-  // the remainder of the reference, otherwise write a record to the debug
+  // |ref_block_itr|.  The validation code first executes a check for the
+  // specific condition called for. If the check passes, it executes
+  // the remainder of the reference, otherwise writes a record to the debug
   // output buffer stream including |function_idx, instruction_idx, stage_idx|
-  // and replace the reference with the null value of the original type. The
+  // and replaces the reference with the null value of the original type. The
   // block at |ref_block_itr| can just be replaced with the blocks in
   // |new_blocks|, which will contain at least two blocks. The last block will
   // comprise all instructions following |ref_inst_itr|,
   // preceded by a phi instruction.
   //
-  // This instrumentation pass utilizes GenDebugStreamWrite() to write its
+  // These instrumentation functions utilize GenDebugDirectRead() to read data
+  // from the debug input buffer, specifically the lengths of variable length
+  // descriptor arrays, and the initialization status of each descriptor.
+  // The format of the debug input buffer is documented in instrument.hpp.
+  //
+  // These instrumentation functions utilize GenDebugStreamWrite() to write its
   // error records. The validation-specific part of the error record will
   // have the format:
   //
@@ -76,15 +95,84 @@
   //
   // The Descriptor Array Size is the size of the descriptor array which was
   // indexed.
-  void GenBindlessCheckCode(
-      BasicBlock::iterator ref_inst_itr,
-      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-      uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
+                          UptrVectorIterator<BasicBlock> ref_block_itr,
+                          uint32_t stage_idx,
+                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
+  void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
+                        UptrVectorIterator<BasicBlock> ref_block_itr,
+                        uint32_t stage_idx,
+                        std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Generate instructions into |builder| to read length of runtime descriptor
+  // array |var_id| from debug input buffer and return id of value.
+  uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
+
+  // Generate instructions into |builder| to read initialization status of
+  // descriptor array |image_id| at |index_id| from debug input buffer and
+  // return id of value.
+  uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id,
+                            InstructionBuilder* builder);
+
+  // Analysis data for descriptor reference components, generated by
+  // AnalyzeDescriptorReference. It is necessary and sufficient for further
+  // analysis and regeneration of the reference.
+  typedef struct ref_analysis {
+    uint32_t desc_load_id;
+    uint32_t image_id;
+    uint32_t load_id;
+    uint32_t ptr_id;
+    uint32_t var_id;
+    uint32_t index_id;
+    Instruction* ref_inst;
+  } ref_analysis;
+
+  // Clone original original reference encapsulated by |ref| into |builder|.
+  // This may generate more than one instruction if neccessary.
+  uint32_t CloneOriginalReference(ref_analysis* ref,
+                                  InstructionBuilder* builder);
+
+  // If |inst| references through an image, return the id of the image it
+  // references through. Else return 0.
+  uint32_t GetImageId(Instruction* inst);
+
+  // Get descriptor type inst of variable |var_inst|.
+  Instruction* GetDescriptorTypeInst(Instruction* var_inst);
+
+  // Analyze descriptor reference |ref_inst| and save components into |ref|.
+  // Return true if |ref_inst| is a descriptor reference, false otherwise.
+  bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref);
+
+  // Generate instrumentation code for generic test result |check_id|, starting
+  // with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
+  // Generate conditional branch to a valid or invalid branch. Generate valid
+  // block which does original reference |ref|. Generate invalid block which
+  // writes debug error output utilizing |ref|, |error_id|, |length_id| and
+  // |stage_idx|. Generate merge block for valid and invalid branches. Kill
+  // original reference.
+  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
+                    uint32_t stage_idx, ref_analysis* ref,
+                    std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Initialize state for instrumenting bindless checking
+  void InitializeInstBindlessCheck();
+
+  // Apply GenBoundsCheckCode to every instruction in module. Then apply
+  // GenInitCheckCode to every instruction in module.
   Pass::Status ProcessImpl();
 
-  // True if VK_EXT_descriptor_indexing is defined
-  bool ext_descriptor_indexing_defined_;
+  // Enable instrumentation of runtime array length checking
+  bool input_length_enabled_;
+
+  // Enable instrumentation of descriptor initialization checking
+  bool input_init_enabled_;
+
+  // Mapping from variable to descriptor set
+  std::unordered_map<uint32_t, uint32_t> var2desc_set_;
+
+  // Mapping from variable to binding
+  std::unordered_map<uint32_t, uint32_t> var2binding_;
 };
 
 }  // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 5f3c5a8..ec73640 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -155,12 +155,6 @@
 }
 
 Instruction* Instruction::GetBaseAddress() const {
-  assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain ||
-          opcode() == SpvOpPtrAccessChain ||
-          opcode() == SpvOpInBoundsAccessChain || opcode() == SpvOpCopyObject ||
-          opcode() == SpvOpImageTexelPointer) &&
-         "GetBaseAddress should only be called on instructions that take a "
-         "pointer or image.");
   uint32_t base = GetSingleWordInOperand(kLoadBaseIndex);
   Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base);
   bool done = false;
@@ -182,24 +176,6 @@
         break;
     }
   }
-
-  switch (opcode()) {
-    case SpvOpLoad:
-    case SpvOpStore:
-    case SpvOpAccessChain:
-    case SpvOpInBoundsAccessChain:
-    case SpvOpPtrAccessChain:
-    case SpvOpImageTexelPointer:
-    case SpvOpCopyObject:
-      // A load or store through a pointer.
-      assert(base_inst->IsValidBasePointer() &&
-             "We cannot have a base pointer come from this load");
-      break;
-    default:
-      // A load or store of an image.
-      assert(base_inst->IsValidBaseImage() && "We are expecting an image.");
-      break;
-  }
   return base_inst;
 }
 
@@ -396,6 +372,11 @@
   return subtype;
 }
 
+Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& i) {
+  i.get()->InsertBefore(this);
+  return i.release();
+}
+
 Instruction* Instruction::InsertBefore(
     std::vector<std::unique_ptr<Instruction>>&& list) {
   Instruction* first_node = list.front().get();
@@ -406,11 +387,6 @@
   return first_node;
 }
 
-Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& i) {
-  i.get()->InsertBefore(this);
-  return i.release();
-}
-
 bool Instruction::IsValidBasePointer() const {
   uint32_t tid = type_id();
   if (tid == 0) {
@@ -507,7 +483,17 @@
 
 bool Instruction::IsFloatingPointFoldingAllowed() const {
   // TODO: Add the rules for kernels.  For now it will be pessimistic.
-  if (!context_->get_feature_mgr()->HasCapability(SpvCapabilityShader)) {
+  // For now, do not support capabilities introduced by SPV_KHR_float_controls.
+  if (!context_->get_feature_mgr()->HasCapability(SpvCapabilityShader) ||
+      context_->get_feature_mgr()->HasCapability(SpvCapabilityDenormPreserve) ||
+      context_->get_feature_mgr()->HasCapability(
+          SpvCapabilityDenormFlushToZero) ||
+      context_->get_feature_mgr()->HasCapability(
+          SpvCapabilitySignedZeroInfNanPreserve) ||
+      context_->get_feature_mgr()->HasCapability(
+          SpvCapabilityRoundingModeRTZ) ||
+      context_->get_feature_mgr()->HasCapability(
+          SpvCapabilityRoundingModeRTE)) {
     return false;
   }
 
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 034da76..d507d6c 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -398,8 +398,13 @@
   inline bool operator!=(const Instruction&) const;
   inline bool operator<(const Instruction&) const;
 
-  Instruction* InsertBefore(std::vector<std::unique_ptr<Instruction>>&& list);
+  // Takes ownership of the instruction owned by |i| and inserts it immediately
+  // before |this|. Returns the insterted instruction.
   Instruction* InsertBefore(std::unique_ptr<Instruction>&& i);
+  // Takes ownership of the instructions in |list| and inserts them in order
+  // immediately before |this|.  Returns the first inserted instruction.
+  // Assumes the list is non-empty.
+  Instruction* InsertBefore(std::vector<std::unique_ptr<Instruction>>&& list);
   using utils::IntrusiveNodeBase<Instruction>::InsertBefore;
 
   // Returns true if |this| is an instruction defining a constant, but not a
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 6935a43..6645a2e 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -17,6 +17,7 @@
 #include "instrument_pass.h"
 
 #include "source/cfa.h"
+#include "source/spirv_constant.h"
 
 namespace {
 
@@ -57,8 +58,7 @@
 }
 
 void InstrumentPass::MovePostludeCode(
-    UptrVectorIterator<BasicBlock> ref_block_itr,
-    std::unique_ptr<BasicBlock>* new_blk_ptr) {
+    UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
   // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
   // Move contents of original ref block.
   for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
@@ -77,7 +77,7 @@
         same_block_post_[rid] = rid;
       }
     }
-    (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+    new_blk_ptr->AddInstruction(std::move(mv_inst));
   }
 }
 
@@ -107,7 +107,7 @@
       builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
                            builder->GetUintConstantId(field_offset));
   uint32_t buf_id = GetOutputBufferId();
-  uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
   Instruction* achain_inst =
       builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder->GetUintConstantId(kDebugOutputDataOffset),
@@ -143,22 +143,21 @@
                           element_val_inst->result_id(), builder);
 }
 
+uint32_t InstrumentPass::GenVarLoad(uint32_t var_id,
+                                    InstructionBuilder* builder) {
+  Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
+  uint32_t type_id = GetPointeeTypeId(var_inst);
+  Instruction* load_inst = builder->AddUnaryOp(type_id, SpvOpLoad, var_id);
+  return load_inst->result_id();
+}
+
 void InstrumentPass::GenBuiltinOutputCode(uint32_t builtin_id,
                                           uint32_t builtin_off,
                                           uint32_t base_offset_id,
                                           InstructionBuilder* builder) {
   // Load and store builtin
-  Instruction* load_inst =
-      builder->AddUnaryOp(GetUintId(), SpvOpLoad, builtin_id);
-  GenDebugOutputFieldCode(base_offset_id, builtin_off, load_inst->result_id(),
-                          builder);
-}
-
-void InstrumentPass::GenUintNullOutputCode(uint32_t field_off,
-                                           uint32_t base_offset_id,
-                                           InstructionBuilder* builder) {
-  GenDebugOutputFieldCode(base_offset_id, field_off,
-                          builder->GetNullId(GetUintId()), builder);
+  uint32_t load_id = GenVarLoad(builtin_id, builder);
+  GenDebugOutputFieldCode(base_offset_id, builtin_off, load_id, builder);
 }
 
 void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx,
@@ -168,43 +167,125 @@
   switch (stage_idx) {
     case SpvExecutionModelVertex: {
       // Load and store VertexId and InstanceId
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexIndex),
-                           kInstVertOutVertexIndex, base_offset_id, builder);
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceIndex),
-                           kInstVertOutInstanceIndex, base_offset_id, builder);
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInVertexIndex),
+          kInstVertOutVertexIndex, base_offset_id, builder);
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInInstanceIndex),
+          kInstVertOutInstanceIndex, base_offset_id, builder);
     } break;
     case SpvExecutionModelGLCompute: {
-      // Load and store GlobalInvocationId. Second word is unused; store zero.
-      GenBuiltinOutputCode(
-          context()->GetBuiltinVarId(SpvBuiltInGlobalInvocationId),
-          kInstCompOutGlobalInvocationId, base_offset_id, builder);
-      GenUintNullOutputCode(kInstCompOutUnused, base_offset_id, builder);
+      // Load and store GlobalInvocationId.
+      uint32_t load_id = GenVarLoad(
+          context()->GetBuiltinInputVarId(SpvBuiltInGlobalInvocationId),
+          builder);
+      Instruction* x_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, load_id, 0);
+      Instruction* y_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, load_id, 1);
+      Instruction* z_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, load_id, 2);
+      if (version_ == 1) {
+        // For version 1 format, as a stopgap, pack uvec3 into first word:
+        // x << 21 | y << 10 | z. Second word is unused. (DEPRECATED)
+        Instruction* x_shft_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpShiftLeftLogical, x_inst->result_id(),
+            builder->GetUintConstantId(21));
+        Instruction* y_shft_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpShiftLeftLogical, y_inst->result_id(),
+            builder->GetUintConstantId(10));
+        Instruction* x_or_y_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpBitwiseOr, x_shft_inst->result_id(),
+            y_shft_inst->result_id());
+        Instruction* x_or_y_or_z_inst =
+            builder->AddBinaryOp(GetUintId(), SpvOpBitwiseOr,
+                                 x_or_y_inst->result_id(), z_inst->result_id());
+        GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationId,
+                                x_or_y_or_z_inst->result_id(), builder);
+      } else {
+        // For version 2 format, write all three words
+        GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdX,
+                                x_inst->result_id(), builder);
+        GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdY,
+                                y_inst->result_id(), builder);
+        GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdZ,
+                                z_inst->result_id(), builder);
+      }
     } break;
     case SpvExecutionModelGeometry: {
       // Load and store PrimitiveId and InvocationId.
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInPrimitiveId),
-                           kInstGeomOutPrimitiveId, base_offset_id, builder);
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
-                           kInstGeomOutInvocationId, base_offset_id, builder);
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInPrimitiveId),
+          kInstGeomOutPrimitiveId, base_offset_id, builder);
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInInvocationId),
+          kInstGeomOutInvocationId, base_offset_id, builder);
     } break;
-    case SpvExecutionModelTessellationControl:
+    case SpvExecutionModelTessellationControl: {
+      // Load and store InvocationId and PrimitiveId
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInInvocationId),
+          kInstTessCtlOutInvocationId, base_offset_id, builder);
+      GenBuiltinOutputCode(
+          context()->GetBuiltinInputVarId(SpvBuiltInPrimitiveId),
+          kInstTessCtlOutPrimitiveId, base_offset_id, builder);
+    } break;
     case SpvExecutionModelTessellationEvaluation: {
-      // Load and store InvocationId. Second word is unused; store zero.
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInvocationId),
-                           kInstTessOutInvocationId, base_offset_id, builder);
-      GenUintNullOutputCode(kInstTessOutUnused, base_offset_id, builder);
+      if (version_ == 1) {
+        // For format version 1, load and store InvocationId.
+        GenBuiltinOutputCode(
+            context()->GetBuiltinInputVarId(SpvBuiltInInvocationId),
+            kInstTessOutInvocationId, base_offset_id, builder);
+      } else {
+        // For format version 2, load and store PrimitiveId and TessCoord.uv
+        GenBuiltinOutputCode(
+            context()->GetBuiltinInputVarId(SpvBuiltInPrimitiveId),
+            kInstTessEvalOutPrimitiveId, base_offset_id, builder);
+        uint32_t load_id = GenVarLoad(
+            context()->GetBuiltinInputVarId(SpvBuiltInTessCoord), builder);
+        Instruction* u_inst = builder->AddIdLiteralOp(
+            GetUintId(), SpvOpCompositeExtract, load_id, 0);
+        Instruction* v_inst = builder->AddIdLiteralOp(
+            GetUintId(), SpvOpCompositeExtract, load_id, 1);
+        GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU,
+                                u_inst->result_id(), builder);
+        GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV,
+                                v_inst->result_id(), builder);
+      }
     } break;
     case SpvExecutionModelFragment: {
       // Load FragCoord and convert to Uint
-      Instruction* frag_coord_inst =
-          builder->AddUnaryOp(GetVec4FloatId(), SpvOpLoad,
-                              context()->GetBuiltinVarId(SpvBuiltInFragCoord));
+      Instruction* frag_coord_inst = builder->AddUnaryOp(
+          GetVec4FloatId(), SpvOpLoad,
+          context()->GetBuiltinInputVarId(SpvBuiltInFragCoord));
       Instruction* uint_frag_coord_inst = builder->AddUnaryOp(
           GetVec4UintId(), SpvOpBitcast, frag_coord_inst->result_id());
       for (uint32_t u = 0; u < 2u; ++u)
         GenFragCoordEltDebugOutputCode(
             base_offset_id, uint_frag_coord_inst->result_id(), u, builder);
     } break;
+    case SpvExecutionModelRayGenerationNV:
+    case SpvExecutionModelIntersectionNV:
+    case SpvExecutionModelAnyHitNV:
+    case SpvExecutionModelClosestHitNV:
+    case SpvExecutionModelMissNV:
+    case SpvExecutionModelCallableNV: {
+      // Load and store LaunchIdNV.
+      uint32_t launch_id = GenVarLoad(
+          context()->GetBuiltinInputVarId(SpvBuiltInLaunchIdNV), builder);
+      Instruction* x_launch_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, launch_id, 0);
+      Instruction* y_launch_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, launch_id, 1);
+      Instruction* z_launch_inst = builder->AddIdLiteralOp(
+          GetUintId(), SpvOpCompositeExtract, launch_id, 2);
+      GenDebugOutputFieldCode(base_offset_id, kInstRayTracingOutLaunchIdX,
+                              x_launch_inst->result_id(), builder);
+      GenDebugOutputFieldCode(base_offset_id, kInstRayTracingOutLaunchIdY,
+                              y_launch_inst->result_id(), builder);
+      GenDebugOutputFieldCode(base_offset_id, kInstRayTracingOutLaunchIdZ,
+                              z_launch_inst->result_id(), builder);
+    } break;
     default: { assert(false && "unsupported stage"); } break;
   }
 }
@@ -222,6 +303,16 @@
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+uint32_t InstrumentPass::GenDebugDirectRead(
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+  // Call debug input function. Pass func_idx and offset ids as args.
+  uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
+  uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
+  std::vector<uint32_t> args = {input_func_id};
+  (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
+  return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+}
+
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
@@ -230,7 +321,7 @@
     std::unique_ptr<Instruction>* inst,
     std::unordered_map<uint32_t, uint32_t>* same_blk_post,
     std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-    std::unique_ptr<BasicBlock>* block_ptr) {
+    BasicBlock* block_ptr) {
   (*inst)->ForEachInId(
       [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
         const auto map_itr = (*same_blk_post).find(*iid);
@@ -247,7 +338,7 @@
             sb_inst->SetResultId(nid);
             (*same_blk_post)[rid] = nid;
             *iid = nid;
-            (*block_ptr)->AddInstruction(std::move(sb_inst));
+            block_ptr->AddInstruction(std::move(sb_inst));
           }
         } else {
           // Reset same-block op operand.
@@ -280,12 +371,12 @@
 }
 
 // Return id for output buffer uint ptr type
-uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
-  if (output_buffer_uint_ptr_id_ == 0) {
-    output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+uint32_t InstrumentPass::GetBufferUintPtrId() {
+  if (buffer_uint_ptr_id_ == 0) {
+    buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
         GetUintId(), SpvStorageClassStorageBuffer);
   }
-  return output_buffer_uint_ptr_id_;
+  return buffer_uint_ptr_id_;
 }
 
 uint32_t InstrumentPass::GetOutputBufferBinding() {
@@ -298,22 +389,74 @@
   return 0;
 }
 
+uint32_t InstrumentPass::GetInputBufferBinding() {
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      return kDebugInputBindingBindless;
+    default:
+      assert(false && "unexpected validation id");
+  }
+  return 0;
+}
+
+analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
+    analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
+  if (uint_rarr_ty_ == nullptr) {
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
+    uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
+    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
+    // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
+    // a block, and will therefore be decorated with an ArrayStride. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+           "used RuntimeArray type returned");
+    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
+  }
+  return uint_rarr_ty_;
+}
+
+void InstrumentPass::AddStorageBufferExt() {
+  if (storage_buffer_ext_defined_) return;
+  if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
+    const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+    const auto num_chars = ext_name.size();
+    // Compute num words, accommodate the terminating null character.
+    const auto num_words = (num_chars + 1 + 3) / 4;
+    std::vector<uint32_t> ext_words(num_words, 0u);
+    std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+    context()->AddExtension(std::unique_ptr<Instruction>(
+        new Instruction(context(), SpvOpExtension, 0u, 0u,
+                        {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+  }
+  storage_buffer_ext_defined_ = true;
+}
+
 // Return id for output buffer
 uint32_t InstrumentPass::GetOutputBufferId() {
   if (output_buffer_id_ == 0) {
     // If not created yet, create one
     analysis::DecorationManager* deco_mgr = get_decoration_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
     analysis::Integer uint_ty(32, false);
     analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
-    analysis::Type* reg_uint_rarr_ty =
-        type_mgr->GetRegisteredType(&uint_rarr_ty);
-    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(reg_uint_rarr_ty);
-    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
-    analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
-    analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
-    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+    analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
+           "used struct type returned");
     deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
     deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
                                   SpvDecorationOffset, 0);
@@ -331,23 +474,62 @@
                                desc_set_);
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
                                GetOutputBufferBinding());
-    // Look for storage buffer extension. If none, create one.
-    if (!get_feature_mgr()->HasExtension(
-            kSPV_KHR_storage_buffer_storage_class)) {
-      const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
-      const auto num_chars = ext_name.size();
-      // Compute num words, accommodate the terminating null character.
-      const auto num_words = (num_chars + 1 + 3) / 4;
-      std::vector<uint32_t> ext_words(num_words, 0u);
-      std::memcpy(ext_words.data(), ext_name.data(), num_chars);
-      context()->AddExtension(std::unique_ptr<Instruction>(
-          new Instruction(context(), SpvOpExtension, 0u, 0u,
-                          {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+    AddStorageBufferExt();
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+      // Add the new buffer to all entry points.
+      for (auto& entry : get_module()->entry_points()) {
+        entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}});
+        context()->AnalyzeUses(&entry);
+      }
     }
   }
   return output_buffer_id_;
 }
 
+uint32_t InstrumentPass::GetInputBufferId() {
+  if (input_buffer_id_ == 0) {
+    // If not created yet, create one
+    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
+    analysis::Struct buf_ty({reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
+           "used struct type returned");
+    deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
+    deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
+    uint32_t ibufTyPtrId_ =
+        type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
+    input_buffer_id_ = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(new Instruction(
+        context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvStorageClassStorageBuffer}}}));
+    context()->AddGlobalValue(std::move(newVarOp));
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
+                               desc_set_);
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
+                               GetInputBufferBinding());
+    AddStorageBufferExt();
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+      // Add the new buffer to all entry points.
+      for (auto& entry : get_module()->entry_points()) {
+        entry.AddOperand({SPV_OPERAND_TYPE_ID, {input_buffer_id_}});
+        context()->AnalyzeUses(&entry);
+      }
+    }
+  }
+  return input_buffer_id_;
+}
+
 uint32_t InstrumentPass::GetVec4FloatId() {
   if (v4float_id_ == 0) {
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -445,9 +627,11 @@
         context(), &*new_blk_ptr,
         IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
     // Gen test if debug output buffer size will not be exceeded.
-    uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
+    uint32_t val_spec_offset =
+        (version_ == 1) ? kInstStageOutCnt : kInst2StageOutCnt;
+    uint32_t obuf_record_sz = val_spec_offset + val_spec_param_cnt;
     uint32_t buf_id = GetOutputBufferId();
-    uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+    uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
     Instruction* obuf_curr_sz_ac_inst =
         builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder.GetUintConstantId(kDebugOutputSizeOffset));
@@ -491,7 +675,7 @@
     GenStageStreamWriteCode(stage_idx, obuf_curr_sz_id, &builder);
     // Gen writes of validation specific data
     for (uint32_t i = 0; i < val_spec_param_cnt; ++i) {
-      GenDebugOutputFieldCode(obuf_curr_sz_id, kInstStageOutCnt + i,
+      GenDebugOutputFieldCode(obuf_curr_sz_id, val_spec_offset + i,
                               param_vec[kInstCommonParamCnt + i], &builder);
     }
     // Close write block and gen merge block
@@ -515,6 +699,81 @@
   return output_func_id_;
 }
 
+uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
+  uint32_t func_id = param2input_func_id_[param_cnt];
+  if (func_id != 0) return func_id;
+  // Create input function for param_cnt
+  func_id = TakeNextId();
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  std::vector<const analysis::Type*> param_types;
+  for (uint32_t c = 0; c < param_cnt; ++c)
+    param_types.push_back(type_mgr->GetType(GetUintId()));
+  analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types);
+  analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+  std::unique_ptr<Instruction> func_inst(new Instruction(
+      get_module()->context(), SpvOpFunction, GetUintId(), func_id,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+        {SpvFunctionControlMaskNone}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+        {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+  std::unique_ptr<Function> input_func =
+      MakeUnique<Function>(std::move(func_inst));
+  // Add parameters
+  std::vector<uint32_t> param_vec;
+  for (uint32_t c = 0; c < param_cnt; ++c) {
+    uint32_t pid = TakeNextId();
+    param_vec.push_back(pid);
+    std::unique_ptr<Instruction> param_inst(new Instruction(
+        get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
+    input_func->AddParameter(std::move(param_inst));
+  }
+  // Create block
+  uint32_t blk_id = TakeNextId();
+  std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
+  std::unique_ptr<BasicBlock> new_blk_ptr =
+      MakeUnique<BasicBlock>(std::move(blk_label));
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  // For each offset parameter, generate new offset with parameter, adding last
+  // loaded value if it exists, and load value from input buffer at new offset.
+  // Return last loaded value.
+  uint32_t buf_id = GetInputBufferId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
+  uint32_t last_value_id = 0;
+  for (uint32_t p = 0; p < param_cnt; ++p) {
+    uint32_t offset_id;
+    if (p == 0) {
+      offset_id = param_vec[0];
+    } else {
+      Instruction* offset_inst = builder.AddBinaryOp(
+          GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]);
+      offset_id = offset_inst->result_id();
+    }
+    Instruction* ac_inst = builder.AddTernaryOp(
+        buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+        builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
+    Instruction* load_inst =
+        builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id());
+    last_value_id = load_inst->result_id();
+  }
+  (void)builder.AddInstruction(MakeUnique<Instruction>(
+      context(), SpvOpReturnValue, 0, 0,
+      std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
+  // Close block and function and add function to module
+  new_blk_ptr->SetParent(&*input_func);
+  input_func->AddBasicBlock(std::move(new_blk_ptr));
+  std::unique_ptr<Instruction> func_end_inst(
+      new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
+  input_func->SetFunctionEnd(std::move(func_end_inst));
+  context()->AddFunction(std::move(input_func));
+  param2input_func_id_[param_cnt] = func_id;
+  return func_id;
+}
+
 bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
                                         InstProcessFunction& pfn) {
   bool modified = false;
@@ -525,21 +784,17 @@
     ++function_idx;
   }
   std::vector<std::unique_ptr<BasicBlock>> new_blks;
-  // Start count after function instruction
-  uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    // Count block's label
-    ++instruction_idx;
-    for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
-      // Bump instruction count if debug instructions
-      instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
+    for (auto ii = bi->begin(); ii != bi->end();) {
       // Generate instrumentation if warranted
-      pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+      pfn(ii, bi, stage_idx, &new_blks);
       if (new_blks.size() == 0) {
         ++ii;
         continue;
       }
+      // Add new blocks to label id map
+      for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
       // If there are new blocks we know there will always be two or
       // more, so update succeeding phis with label of new last block.
       size_t newBlocksSize = new_blks.size();
@@ -569,6 +824,9 @@
                                                   uint32_t stage_idx) {
   bool modified = false;
   std::unordered_set<uint32_t> done;
+  // Don't process input and output functions
+  for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
+  if (output_func_id_ != 0) done.insert(output_func_id_);
   // Process all functions from roots
   while (!roots->empty()) {
     const uint32_t fi = roots->front();
@@ -607,7 +865,12 @@
       stage != SpvExecutionModelGeometry &&
       stage != SpvExecutionModelGLCompute &&
       stage != SpvExecutionModelTessellationControl &&
-      stage != SpvExecutionModelTessellationEvaluation)
+      stage != SpvExecutionModelTessellationEvaluation &&
+      stage != SpvExecutionModelRayGenerationNV &&
+      stage != SpvExecutionModelIntersectionNV &&
+      stage != SpvExecutionModelAnyHitNV &&
+      stage != SpvExecutionModelClosestHitNV &&
+      stage != SpvExecutionModelMissNV && stage != SpvExecutionModelCallableNV)
     return false;
   // Add together the roots of all entry points
   std::queue<uint32_t> roots;
@@ -620,14 +883,17 @@
 
 void InstrumentPass::InitializeInstrument() {
   output_buffer_id_ = 0;
-  output_buffer_uint_ptr_id_ = 0;
+  buffer_uint_ptr_id_ = 0;
   output_func_id_ = 0;
   output_func_param_cnt_ = 0;
+  input_buffer_id_ = 0;
   v4float_id_ = 0;
   uint_id_ = 0;
   v4uint_id_ = 0;
   bool_id_ = 0;
   void_id_ = 0;
+  storage_buffer_ext_defined_ = false;
+  uint_rarr_ty_ = nullptr;
 
   // clear collections
   id2function_.clear();
@@ -641,70 +907,68 @@
     }
   }
 
-  // Calculate instruction offset of first function
-  uint32_t pre_func_size = 0;
+  // Remember original instruction offsets
+  uint32_t module_offset = 0;
   Module* module = get_module();
   for (auto& i : context()->capabilities()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->extensions()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->ext_inst_imports()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
-  ++pre_func_size;  // memory_model
+  ++module_offset;  // memory_model
   for (auto& i : module->entry_points()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->execution_modes()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs1()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs2()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs3()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->annotations()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->types_values()) {
-    pre_func_size += 1;
-    pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
+    module_offset += 1;
+    module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
   }
-  funcIdx2offset_[0] = pre_func_size;
 
-  // Set instruction offsets for all other functions.
-  uint32_t func_idx = 1;
-  auto prev_fn = get_module()->begin();
-  auto curr_fn = prev_fn;
-  for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
-    // Count function and end instructions
-    uint32_t func_size = 2;
-    for (auto& blk : *prev_fn) {
+  auto curr_fn = get_module()->begin();
+  for (; curr_fn != get_module()->end(); ++curr_fn) {
+    // Count function instruction
+    module_offset += 1;
+    curr_fn->ForEachParam(
+        [&module_offset](const Instruction*) { module_offset += 1; }, true);
+    for (auto& blk : *curr_fn) {
       // Count label
-      func_size += 1;
+      module_offset += 1;
       for (auto& inst : blk) {
-        func_size += 1;
-        func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        uid2offset_[inst.unique_id()] = module_offset;
+        module_offset += 1;
       }
     }
-    funcIdx2offset_[func_idx] = func_size;
-    ++prev_fn;
-    ++func_idx;
+    // Count function end instruction
+    module_offset += 1;
   }
 }
 
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index cfa76b1..d255698 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -65,31 +65,31 @@
   using cbb_ptr = const BasicBlock*;
 
  public:
-  using InstProcessFunction = std::function<void(
-      BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
-      std::vector<std::unique_ptr<BasicBlock>>*)>;
+  using InstProcessFunction =
+      std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
+                         uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
 
   ~InstrumentPass() override = default;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse |
-           IRContext::kAnalysisInstrToBlockMapping |
-           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId |
-           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
   }
 
  protected:
-  // Create instrumentation pass which utilizes descriptor set |desc_set|
-  // for debug input and output buffers and writes |shader_id| into debug
-  // output records.
-  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
+  // Create instrumentation pass for |validation_id| which utilizes descriptor
+  // set |desc_set| for debug input and output buffers and writes |shader_id|
+  // into debug output records with format |version|.
+  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id,
+                 uint32_t version)
       : Pass(),
         desc_set_(desc_set),
         shader_id_(shader_id),
-        validation_id_(validation_id) {}
+        validation_id_(validation_id),
+        version_(version) {}
 
-  // Initialize state for instrumentation of module by |validation_id|.
+  // Initialize state for instrumentation of module.
   void InitializeInstrument();
 
   // Call |pfn| on all instructions in all functions in the call tree of the
@@ -107,7 +107,7 @@
   // Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
   // to be instrumented into block |new_blk_ptr|.
   void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
-                        std::unique_ptr<BasicBlock>* new_blk_ptr);
+                        BasicBlock* new_blk_ptr);
 
   // Generate instructions in |builder| which will atomically fetch and
   // increment the size of the debug output buffer stream of the current
@@ -148,6 +148,7 @@
   //     Stage
   //     Stage-specific Word 0
   //     Stage-specific Word 1
+  //     ...
   //     Validation Error Code
   //     Validation-specific Word 0
   //     Validation-specific Word 1
@@ -172,12 +173,12 @@
   // following Stage-specific words.
   //
   // The Stage-specific Words identify which invocation of the shader generated
-  // the error. Every stage will write two words, although in some cases the
-  // second word is unused and so zero is written. Vertex shaders will write
-  // the Vertex and Instance ID. Fragment shaders will write FragCoord.xy.
-  // Compute shaders will write the Global Invocation ID and zero (unused).
-  // Both tesselation shaders will write the Invocation Id and zero (unused).
-  // The geometry shader will write the Primitive ID and Invocation ID.
+  // 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
+  // Primitive ID and Invocation ID.
   //
   // The Validation Error Code specifies the exact error which has occurred.
   // These are enumerated with the kInstError* static consts. This allows
@@ -195,6 +196,17 @@
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Generate in |builder| instructions to read the unsigned integer from the
+  // input buffer specified by the offsets in |offset_ids|. Given offsets
+  // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
+  //
+  // ibuf[...ibuf[ibuf[o0]+o1]...+oN]
+  //
+  // The binding and the format of the input buffer is determined by each
+  // specific validation, which is specified at the creation of the pass.
+  uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
+                              InstructionBuilder* builder);
+
   // Generate code to cast |value_id| to unsigned, if needed. Return
   // an id to the unsigned equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@@ -211,15 +223,28 @@
   // Return id for void type
   uint32_t GetVoidId();
 
-  // Return id for output buffer uint type
-  uint32_t GetOutputBufferUintPtrId();
+  // Return pointer to type for runtime array of uint
+  analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
+                                          analysis::TypeManager* type_mgr);
+
+  // Return id for buffer uint type
+  uint32_t GetBufferUintPtrId();
 
   // Return binding for output buffer for current validation.
   uint32_t GetOutputBufferBinding();
 
+  // Return binding for input buffer for current validation.
+  uint32_t GetInputBufferBinding();
+
+  // Add storage buffer extension if needed
+  void AddStorageBufferExt();
+
   // Return id for debug output buffer
   uint32_t GetOutputBufferId();
 
+  // Return id for debug input buffer
+  uint32_t GetInputBufferId();
+
   // Return id for v4float type
   uint32_t GetVec4FloatId();
 
@@ -227,10 +252,14 @@
   uint32_t GetVec4UintId();
 
   // Return id for output function. Define if it doesn't exist with
-  // |val_spec_arg_cnt| validation-specific uint32 arguments.
+  // |val_spec_param_cnt| validation-specific uint32 parameters.
   uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
                                     uint32_t val_spec_param_cnt);
 
+  // Return id for input function taking |param_cnt| uint32 parameters. Define
+  // if it doesn't exist.
+  uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
+
   // Apply instrumentation function |pfn| to every instruction in |func|.
   // If code is generated for an instruction, replace the instruction's
   // block with the new blocks that are generated. Continue processing at the
@@ -265,16 +294,15 @@
                                       uint32_t component,
                                       InstructionBuilder* builder);
 
+  // Generate instructions into |builder| which will load |var_id| and return
+  // its result id.
+  uint32_t GenVarLoad(uint32_t var_id, InstructionBuilder* builder);
+
   // Generate instructions into |builder| which will load the uint |builtin_id|
   // and write it into the debug output buffer at |base_off| + |builtin_off|.
   void GenBuiltinOutputCode(uint32_t builtin_id, uint32_t builtin_off,
                             uint32_t base_off, InstructionBuilder* builder);
 
-  // Generate instructions into |builder| which will write a uint null into
-  // the debug output buffer at |base_off| + |builtin_off|.
-  void GenUintNullOutputCode(uint32_t field_off, uint32_t base_off,
-                             InstructionBuilder* builder);
-
   // Generate instructions into |builder| which will write the |stage_idx|-
   // specific members of the debug output stream at |base_off|.
   void GenStageStreamWriteCode(uint32_t stage_idx, uint32_t base_off,
@@ -292,7 +320,7 @@
       std::unique_ptr<Instruction>* inst,
       std::unordered_map<uint32_t, uint32_t>* same_blk_post,
       std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-      std::unique_ptr<BasicBlock>* block_ptr);
+      BasicBlock* block_ptr);
 
   // Update phis in succeeding blocks to point to new last block
   void UpdateSucceedingPhis(
@@ -311,8 +339,8 @@
   // CFG. It has functionality not present in CFG. Consolidate.
   std::unordered_map<uint32_t, BasicBlock*> id2block_;
 
-  // Map from function's position index to the offset of its first instruction
-  std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
+  // Map from instruction's unique id to offset in original file.
+  std::unordered_map<uint32_t, uint32_t> uid2offset_;
 
   // result id for OpConstantFalse
   uint32_t validation_id_;
@@ -321,14 +349,20 @@
   uint32_t output_buffer_id_;
 
   // type id for output buffer element
-  uint32_t output_buffer_uint_ptr_id_;
+  uint32_t buffer_uint_ptr_id_;
 
   // id for debug output function
   uint32_t output_func_id_;
 
+  // ids for debug input functions
+  std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
+
   // param count for output function
   uint32_t output_func_param_cnt_;
 
+  // id for input buffer variable
+  uint32_t input_buffer_id_;
+
   // id for v4float type
   uint32_t v4float_id_;
 
@@ -344,6 +378,15 @@
   // id for void type
   uint32_t void_id_;
 
+  // Record format version
+  uint32_t version_;
+
+  // boolean to remember storage buffer extension
+  bool storage_buffer_ext_defined_;
+
+  // runtime array of uint type
+  analysis::Type* uint_rarr_ty_;
+
   // Pre-instrumentation same-block insts
   std::unordered_map<uint32_t, Instruction*> same_block_pre_;
 
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index a31349f..20309ca 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -89,6 +89,11 @@
 }
 
 void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
+  // The ConstantManager contains Type pointers. If the TypeManager goes
+  // away, the ConstantManager has to go away.
+  if (analyses_to_invalidate & kAnalysisTypes) {
+    analyses_to_invalidate |= kAnalysisConstants;
+  }
   if (analyses_to_invalidate & kAnalysisDefUse) {
     def_use_mgr_.reset(nullptr);
   }
@@ -127,9 +132,6 @@
     constant_mgr_.reset(nullptr);
   }
   if (analyses_to_invalidate & kAnalysisTypes) {
-    // The ConstantManager contains Type pointers. If the TypeManager goes
-    // away, the ConstantManager has to go away.
-    constant_mgr_.reset(nullptr);
     type_mgr_.reset(nullptr);
   }
 
@@ -619,7 +621,7 @@
   return &it->second;
 }
 
-uint32_t IRContext::FindBuiltinVar(uint32_t builtin) {
+uint32_t IRContext::FindBuiltinInputVar(uint32_t builtin) {
   for (auto& a : module_->annotations()) {
     if (a.opcode() != SpvOpDecorate) continue;
     if (a.GetSingleWordInOperand(kSpvDecorateDecorationInIdx) !=
@@ -629,6 +631,7 @@
     uint32_t target_id = a.GetSingleWordInOperand(kSpvDecorateTargetIdInIdx);
     Instruction* b_var = get_def_use_mgr()->GetDef(target_id);
     if (b_var->opcode() != SpvOpVariable) continue;
+    if (b_var->GetSingleWordInOperand(0) != SpvStorageClassInput) continue;
     return target_id;
   }
   return 0;
@@ -651,14 +654,14 @@
   }
 }
 
-uint32_t IRContext::GetBuiltinVarId(uint32_t builtin) {
+uint32_t IRContext::GetBuiltinInputVarId(uint32_t builtin) {
   if (!AreAnalysesValid(kAnalysisBuiltinVarId)) ResetBuiltinAnalysis();
   // If cached, return it.
   std::unordered_map<uint32_t, uint32_t>::iterator it =
       builtin_var_id_map_.find(builtin);
   if (it != builtin_var_id_map_.end()) return it->second;
   // Look for one in shader
-  uint32_t var_id = FindBuiltinVar(builtin);
+  uint32_t var_id = FindBuiltinInputVar(builtin);
   if (var_id == 0) {
     // If not found, create it
     // TODO(greg-lunarg): Add support for all builtins
@@ -675,12 +678,19 @@
       case SpvBuiltInVertexIndex:
       case SpvBuiltInInstanceIndex:
       case SpvBuiltInPrimitiveId:
-      case SpvBuiltInInvocationId:
-      case SpvBuiltInGlobalInvocationId: {
+      case SpvBuiltInInvocationId: {
         analysis::Integer uint_ty(32, false);
         reg_type = type_mgr->GetRegisteredType(&uint_ty);
         break;
       }
+      case SpvBuiltInGlobalInvocationId:
+      case SpvBuiltInLaunchIdNV: {
+        analysis::Integer uint_ty(32, false);
+        analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+        analysis::Vector v3uint_ty(reg_uint_ty, 3);
+        reg_type = type_mgr->GetRegisteredType(&v3uint_ty);
+        break;
+      }
       default: {
         assert(false && "unhandled builtin");
         return 0;
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 185b494..37c6449 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -100,7 +100,9 @@
         constant_mgr_(nullptr),
         type_mgr_(nullptr),
         id_to_name_(nullptr),
-        max_id_bound_(kDefaultMaxIdBound) {
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {
     SetContextMessageConsumer(syntax_context_, consumer_);
     module_->SetContext(this);
   }
@@ -115,7 +117,9 @@
         valid_analyses_(kAnalysisNone),
         type_mgr_(nullptr),
         id_to_name_(nullptr),
-        max_id_bound_(kDefaultMaxIdBound) {
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {
     SetContextMessageConsumer(syntax_context_, consumer_);
     module_->SetContext(this);
     InitializeCombinators();
@@ -456,7 +460,16 @@
 
   // Return the next available SSA id and increment it.  Returns 0 if the
   // maximum SSA id has been reached.
-  inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); }
+  inline uint32_t TakeNextId() {
+    uint32_t next_id = module()->TakeNextIdBound();
+    if (next_id == 0) {
+      if (consumer()) {
+        std::string message = "ID overflow. Try running compact-ids.";
+        consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+      }
+    }
+    return next_id;
+  }
 
   FeatureManager* get_feature_mgr() {
     if (!feature_mgr_.get()) {
@@ -482,10 +495,20 @@
   uint32_t max_id_bound() const { return max_id_bound_; }
   void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; }
 
-  // Return id of variable only decorated with |builtin|, if in module.
+  bool preserve_bindings() const { return preserve_bindings_; }
+  void set_preserve_bindings(bool should_preserve_bindings) {
+    preserve_bindings_ = should_preserve_bindings;
+  }
+
+  bool preserve_spec_constants() const { return preserve_spec_constants_; }
+  void set_preserve_spec_constants(bool should_preserve_spec_constants) {
+    preserve_spec_constants_ = should_preserve_spec_constants;
+  }
+
+  // Return id of input variable only decorated with |builtin|, if in module.
   // Create variable and return its id otherwise. If builtin not currently
   // supported, return 0.
-  uint32_t GetBuiltinVarId(uint32_t builtin);
+  uint32_t GetBuiltinInputVarId(uint32_t builtin);
 
   // Returns the function whose id is |id|, if one exists.  Returns |nullptr|
   // otherwise.
@@ -648,9 +671,9 @@
   // true if the cfg is invalidated.
   bool CheckCFG();
 
-  // Return id of variable only decorated with |builtin|, if in module.
+  // Return id of input variable only decorated with |builtin|, if in module.
   // Return 0 otherwise.
-  uint32_t FindBuiltinVar(uint32_t builtin);
+  uint32_t FindBuiltinInputVar(uint32_t builtin);
 
   // Add |var_id| to all entry points in module.
   void AddVarToEntryPoints(uint32_t var_id);
@@ -741,6 +764,13 @@
 
   // The maximum legal value for the id bound.
   uint32_t max_id_bound_;
+
+  // Whether all bindings within |module_| should be preserved.
+  bool preserve_bindings_;
+
+  // Whether all specialization constants within |module_|
+  // should be preserved.
+  bool preserve_spec_constants_;
 };
 
 inline IRContext::Analysis operator|(IRContext::Analysis lhs,
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 46e2bee..edd4832 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -116,8 +116,10 @@
                  opcode == SpvOpUndef) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else {
-        SPIRV_UNIMPLEMENTED(consumer_,
-                            "unhandled inst type outside function definition");
+        Errorf(consumer_, src, loc,
+               "Unhandled inst type (opcode: %d) found outside function definition.",
+               opcode);
+        return false;
       }
     } else {
       if (block_ == nullptr) {  // Inside function but outside blocks
diff --git a/source/opt/legalize_vector_shuffle_pass.cpp b/source/opt/legalize_vector_shuffle_pass.cpp
new file mode 100644
index 0000000..b5d5d59
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/legalize_vector_shuffle_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status LegalizeVectorShufflePass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([&changed](Instruction* inst) {
+    if (inst->opcode() != SpvOpVectorShuffle) return;
+
+    for (uint32_t idx = 2; idx < inst->NumInOperands(); ++idx) {
+      auto literal = inst->GetSingleWordInOperand(idx);
+      if (literal != 0xFFFFFFFF) continue;
+      changed = true;
+      inst->SetInOperand(idx, {0});
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/legalize_vector_shuffle_pass.h b/source/opt/legalize_vector_shuffle_pass.h
new file mode 100644
index 0000000..ca6e1df
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+#define SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts any usages of 0xFFFFFFFF for the literals in OpVectorShuffle to a
+// literal 0. This is needed because using OxFFFFFFFF is forbidden by the WebGPU
+// spec. 0xFFFFFFFF in the main spec indicates that the result for this
+// component has no source, thus is undefined. Since this is undefined
+// behaviour we are free to use 0.
+class LegalizeVectorShufflePass : public Pass {
+ public:
+  const char* name() const override { return "legalize-vector-shuffle"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 5b976a1..6eb130e 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -264,6 +264,12 @@
 }
 
 bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
+  // This capability can now exist without the extension, so we have to check
+  // for the capability.  This pass is only looking at function scope symbols,
+  // so we do not care if there are variable pointers on storage buffers.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointers))
+    return false;
   // If any extension not in whitelist, return false
   for (auto& ei : get_module()->extensions()) {
     const char* extName =
@@ -337,6 +343,7 @@
       "SPV_AMD_gpu_shader_half_float_fetch",
       "SPV_GOOGLE_decorate_string",
       "SPV_GOOGLE_hlsl_functionality1",
+      "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 9330ab7..cc1b837 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -187,6 +187,7 @@
   // Assumes relaxed logical addressing only (see instruction.h).
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -233,8 +234,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
@@ -246,6 +246,7 @@
       "SPV_AMD_gpu_shader_half_float_fetch",
       "SPV_GOOGLE_decorate_string",
       "SPV_GOOGLE_hlsl_functionality1",
+      "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 6c09dec..f47777e 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -98,8 +98,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
index 8209aa4..299bbe0 100644
--- a/source/opt/local_ssa_elim_pass.cpp
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -83,8 +83,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
@@ -96,6 +95,7 @@
       "SPV_AMD_gpu_shader_half_float_fetch",
       "SPV_GOOGLE_decorate_string",
       "SPV_GOOGLE_hlsl_functionality1",
+      "SPV_GOOGLE_user_type",
       "SPV_NV_shader_subgroup_partitioned",
       "SPV_EXT_descriptor_indexing",
       "SPV_NV_fragment_shader_barycentric",
diff --git a/source/opt/log.h b/source/opt/log.h
index f87cbf3..6805100 100644
--- a/source/opt/log.h
+++ b/source/opt/log.h
@@ -54,18 +54,19 @@
 
 // Logs an error message to the consumer saying the given feature is
 // unimplemented.
-#define SPIRV_UNIMPLEMENTED(consumer, feature)                  \
-  do {                                                          \
-    spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__,   \
-                  {__LINE__, 0, 0}, "unimplemented: " feature); \
+#define SPIRV_UNIMPLEMENTED(consumer, feature)                \
+  do {                                                        \
+    spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \
+                  {static_cast<size_t>(__LINE__), 0, 0},      \
+                  "unimplemented: " feature);                 \
   } while (0)
 
 // Logs an error message to the consumer saying the code location
 // should be unreachable.
-#define SPIRV_UNREACHABLE(consumer)                           \
-  do {                                                        \
-    spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \
-                  {__LINE__, 0, 0}, "unreachable");           \
+#define SPIRV_UNREACHABLE(consumer)                                      \
+  do {                                                                   \
+    spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__,            \
+                  {static_cast<size_t>(__LINE__), 0, 0}, "unreachable"); \
   } while (0)
 
 // Helper macros for concatenating arguments.
@@ -154,32 +155,34 @@
   PP_EXPAND(SPIRV_CONCATENATE(SPIRV_DEBUG_, PP_NARGS(__VA_ARGS__))( \
       consumer, __VA_ARGS__))
 
-#define SPIRV_ASSERT_1(consumer, condition)                             \
-  do {                                                                  \
-    if (!(condition)) {                                                 \
-      spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__,         \
-                    {__LINE__, 0, 0}, "assertion failed: " #condition); \
-      std::exit(EXIT_FAILURE);                                          \
-    }                                                                   \
+#define SPIRV_ASSERT_1(consumer, condition)                     \
+  do {                                                          \
+    if (!(condition)) {                                         \
+      spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \
+                    {static_cast<size_t>(__LINE__), 0, 0},      \
+                    "assertion failed: " #condition);           \
+      std::exit(EXIT_FAILURE);                                  \
+    }                                                           \
   } while (0)
 
-#define SPIRV_ASSERT_2(consumer, condition, message)                 \
-  do {                                                               \
-    if (!(condition)) {                                              \
-      spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__,      \
-                    {__LINE__, 0, 0}, "assertion failed: " message); \
-      std::exit(EXIT_FAILURE);                                       \
-    }                                                                \
+#define SPIRV_ASSERT_2(consumer, condition, message)            \
+  do {                                                          \
+    if (!(condition)) {                                         \
+      spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \
+                    {static_cast<size_t>(__LINE__), 0, 0},      \
+                    "assertion failed: " message);              \
+      std::exit(EXIT_FAILURE);                                  \
+    }                                                           \
   } while (0)
 
-#define SPIRV_ASSERT_more(consumer, condition, format, ...)         \
-  do {                                                              \
-    if (!(condition)) {                                             \
-      spvtools::Logf(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__,    \
-                     {__LINE__, 0, 0}, "assertion failed: " format, \
-                     __VA_ARGS__);                                  \
-      std::exit(EXIT_FAILURE);                                      \
-    }                                                               \
+#define SPIRV_ASSERT_more(consumer, condition, format, ...)      \
+  do {                                                           \
+    if (!(condition)) {                                          \
+      spvtools::Logf(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \
+                     {static_cast<size_t>(__LINE__), 0, 0},      \
+                     "assertion failed: " format, __VA_ARGS__);  \
+      std::exit(EXIT_FAILURE);                                   \
+    }                                                            \
   } while (0)
 
 #define SPIRV_ASSERT_3(consumer, condition, format, ...) \
@@ -191,16 +194,17 @@
 #define SPIRV_ASSERT_5(consumer, condition, format, ...) \
   SPIRV_ASSERT_more(consumer, condition, format, __VA_ARGS__)
 
-#define SPIRV_DEBUG_1(consumer, message)                               \
-  do {                                                                 \
-    spvtools::Log(consumer, SPV_MSG_DEBUG, __FILE__, {__LINE__, 0, 0}, \
-                  message);                                            \
+#define SPIRV_DEBUG_1(consumer, message)                           \
+  do {                                                             \
+    spvtools::Log(consumer, SPV_MSG_DEBUG, __FILE__,               \
+                  {static_cast<size_t>(__LINE__), 0, 0}, message); \
   } while (0)
 
-#define SPIRV_DEBUG_more(consumer, format, ...)                         \
-  do {                                                                  \
-    spvtools::Logf(consumer, SPV_MSG_DEBUG, __FILE__, {__LINE__, 0, 0}, \
-                   format, __VA_ARGS__);                                \
+#define SPIRV_DEBUG_more(consumer, format, ...)                   \
+  do {                                                            \
+    spvtools::Logf(consumer, SPV_MSG_DEBUG, __FILE__,             \
+                   {static_cast<size_t>(__LINE__), 0, 0}, format, \
+                   __VA_ARGS__);                                  \
   } while (0)
 
 #define SPIRV_DEBUG_2(consumer, format, ...) \
diff --git a/source/opt/loop_dependence.h b/source/opt/loop_dependence.h
index 582c8d0..03a9075 100644
--- a/source/opt/loop_dependence.h
+++ b/source/opt/loop_dependence.h
@@ -181,19 +181,21 @@
 
   bool operator!=(const Constraint& other) const;
 
+// clang-format off
 #define DeclareCastMethod(target)                  \
   virtual target* As##target() { return nullptr; } \
   virtual const target* As##target() const { return nullptr; }
-  DeclareCastMethod(DependenceLine);
-  DeclareCastMethod(DependenceDistance);
-  DeclareCastMethod(DependencePoint);
-  DeclareCastMethod(DependenceNone);
-  DeclareCastMethod(DependenceEmpty);
+  DeclareCastMethod(DependenceLine)
+  DeclareCastMethod(DependenceDistance)
+  DeclareCastMethod(DependencePoint)
+  DeclareCastMethod(DependenceNone)
+  DeclareCastMethod(DependenceEmpty)
 #undef DeclareCastMethod
 
  protected:
   const Loop* loop_;
 };
+// clang-format on
 
 class DependenceLine : public Constraint {
  public:
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index 4f49c70..18c49f5 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -36,7 +36,13 @@
   ProcessFunction pfn = [&failed, is_shader, this](Function* function) {
     std::vector<BasicBlock*> return_blocks = CollectReturnBlocks(function);
     if (return_blocks.size() <= 1) {
-      return false;
+      if (!is_shader || return_blocks.size() == 0) {
+        return false;
+      }
+      if (context()->GetStructuredCFGAnalysis()->ContainingConstruct(
+              return_blocks[0]->id()) == 0) {
+        return false;
+      }
     }
 
     function_ = function;
@@ -75,6 +81,7 @@
     return false;
   }
 
+  RecordImmediateDominators(function);
   AddDummyLoopAroundFunction();
 
   std::list<BasicBlock*> order;
@@ -197,6 +204,7 @@
       tail_opcode == SpvOpUnreachable) {
     assert(CurrentState().InLoop() && "Should be in the dummy loop.");
     BranchToBlock(block, CurrentState().LoopMergeId());
+    return_blocks_.insert(block->id());
   }
 }
 
@@ -217,6 +225,7 @@
   return_inst->SetOpcode(SpvOpBranch);
   return_inst->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {target}}});
   context()->get_def_use_mgr()->AnalyzeInstDefUse(return_inst);
+  new_edges_[target_block].insert(block->id());
   cfg()->AddEdge(block->id(), target);
 }
 
@@ -228,21 +237,15 @@
     inst->AddOperand({SPV_OPERAND_TYPE_ID, {new_source->id()}});
     context()->UpdateDefUse(inst);
   });
-
-  const auto& target_pred = cfg()->preds(target->id());
-  if (target_pred.size() == 1) {
-    MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
-  }
 }
 
 void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
-                                            uint32_t predecessor,
                                             Instruction& inst) {
   DominatorAnalysis* dom_tree =
       context()->GetDominatorAnalysis(merge_block->GetParent());
-  BasicBlock* inst_bb = context()->get_instr_block(&inst);
 
   if (inst.result_id() != 0) {
+    BasicBlock* inst_bb = context()->get_instr_block(&inst);
     std::vector<Instruction*> users_to_update;
     context()->get_def_use_mgr()->ForEachUser(
         &inst,
@@ -275,22 +278,23 @@
 
     // There is at least one values that needs to be replaced.
     // First create the OpPhi instruction.
-    InstructionBuilder builder(context(), &*merge_block->begin(),
-                               IRContext::kAnalysisDefUse);
+    InstructionBuilder builder(
+        context(), &*merge_block->begin(),
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
     uint32_t undef_id = Type2Undef(inst.type_id());
     std::vector<uint32_t> phi_operands;
+    const std::set<uint32_t>& new_edges = new_edges_[merge_block];
 
-    // Add the operands for the defining instructions.
-    phi_operands.push_back(inst.result_id());
-    phi_operands.push_back(predecessor);
-
-    // Add undef from all other blocks.
+    // Add the OpPhi operands. If the predecessor is a return block use undef,
+    // otherwise use |inst|'s id.
     std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
     for (uint32_t pred_id : preds) {
-      if (pred_id != predecessor) {
+      if (new_edges.count(pred_id)) {
         phi_operands.push_back(undef_id);
-        phi_operands.push_back(pred_id);
+      } else {
+        phi_operands.push_back(inst.result_id());
       }
+      phi_operands.push_back(pred_id);
     }
 
     Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
@@ -399,8 +403,16 @@
   // Forget about the edges leaving block.  They will be removed.
   cfg()->RemoveSuccessorEdges(block);
 
-  BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
+  auto old_body_id = TakeNextId();
+  BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter);
   predicated->insert(old_body);
+  cfg()->AddEdges(old_body);
+
+  // If a return block is being split, mark the new body block also as a return
+  // block.
+  if (return_blocks_.count(block->id())) {
+    return_blocks_.insert(old_body_id);
+  }
 
   // If |block| was a continue target for a loop |old_body| is now the correct
   // continue target.
@@ -435,14 +447,15 @@
   builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(),
                                old_body->id());
 
-  // 3. Update OpPhi instructions in |merge_block|.
-  BasicBlock* merge_original_pred = MarkedSinglePred(merge_block);
-  if (merge_original_pred == nullptr) {
-    UpdatePhiNodes(block, merge_block);
-  } else if (merge_original_pred == block) {
-    MarkForNewPhiNodes(merge_block, old_body);
+  if (!new_edges_[merge_block].insert(block->id()).second) {
+    // It is possible that we already inserted a new edge to the merge block.
+    // If so, that edge now goes from |old_body| to |merge_block|.
+    new_edges_[merge_block].insert(old_body->id());
   }
 
+  // 3. Update OpPhi instructions in |merge_block|.
+  UpdatePhiNodes(block, merge_block);
+
   // 4. Update the CFG.  We do this after updating the OpPhi instructions
   // because |UpdatePhiNodes| assumes the edge from |block| has not been added
   // to the CFG yet.
@@ -638,36 +651,54 @@
 }
 
 void MergeReturnPass::AddNewPhiNodes() {
-  DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function_);
   std::list<BasicBlock*> order;
   cfg()->ComputeStructuredOrder(function_, &*function_->begin(), &order);
 
   for (BasicBlock* bb : order) {
-    BasicBlock* dominator = dom_tree->ImmediateDominator(bb);
-    if (dominator) {
-      AddNewPhiNodes(bb, new_merge_nodes_[bb], dominator->id());
-    }
+    AddNewPhiNodes(bb);
   }
 }
 
-void MergeReturnPass::AddNewPhiNodes(BasicBlock* bb, BasicBlock* pred,
-                                     uint32_t header_id) {
-  DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function_);
-  // Insert as a stopping point.  We do not have to add anything in the block
-  // or above because the header dominates |bb|.
+void MergeReturnPass::AddNewPhiNodes(BasicBlock* bb) {
+  // New phi nodes are needed for any id whose definition used to dominate |bb|,
+  // but no longer dominates |bb|.  These are found by walking the dominator
+  // tree starting at the original immediate dominator of |bb| and ending at its
+  // current dominator.
 
-  BasicBlock* current_bb = pred;
-  while (current_bb != nullptr && current_bb->id() != header_id) {
+  // Because we are walking the updated dominator tree it is important that the
+  // new phi nodes for the original dominators of |bb| have already been added.
+  // Otherwise some ids might be missed.  Consider the case where bb1 dominates
+  // bb2, and bb2 dominates bb3.  Suppose there are changes such that bb1 no
+  // longer dominates bb2 and the same for bb2 and bb3.  This algorithm will not
+  // look at the ids defined in bb1.  However, calling |AddNewPhiNodes(bb2)|
+  // first will add a phi node in bb2 for that value.  Then a call to
+  // |AddNewPhiNodes(bb3)| will process that value by processing the phi in bb2.
+  DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function_);
+
+  BasicBlock* dominator = dom_tree->ImmediateDominator(bb);
+  if (dominator == nullptr) {
+    return;
+  }
+
+  BasicBlock* current_bb = context()->get_instr_block(original_dominator_[bb]);
+  while (current_bb != nullptr && current_bb != dominator) {
     for (Instruction& inst : *current_bb) {
-      CreatePhiNodesForInst(bb, pred->id(), inst);
+      CreatePhiNodesForInst(bb, inst);
     }
     current_bb = dom_tree->ImmediateDominator(current_bb);
   }
 }
 
-void MergeReturnPass::MarkForNewPhiNodes(BasicBlock* block,
-                                         BasicBlock* single_original_pred) {
-  new_merge_nodes_[block] = single_original_pred;
+void MergeReturnPass::RecordImmediateDominators(Function* function) {
+  DominatorAnalysis* dom_tree = context()->GetDominatorAnalysis(function);
+  for (BasicBlock& bb : *function) {
+    BasicBlock* dominator_bb = dom_tree->ImmediateDominator(&bb);
+    if (dominator_bb && dominator_bb != cfg()->pseudo_entry_block()) {
+      original_dominator_[&bb] = dominator_bb->terminator();
+    } else {
+      original_dominator_[&bb] = nullptr;
+    }
+  }
 }
 
 void MergeReturnPass::InsertAfterElement(BasicBlock* element,
@@ -789,12 +820,7 @@
 
     StructuredCFGAnalysis* struct_cfg_analysis =
         context()->GetStructuredCFGAnalysis();
-    if (struct_cfg_analysis->IsMergeBlock(bb.id())) {
-      // |bb| must be an empty block ending with OpUnreachable.
-      if (bb.begin()->opcode() != SpvOpUnreachable) {
-        return true;
-      }
-    } else if (struct_cfg_analysis->IsContinueBlock(bb.id())) {
+    if (struct_cfg_analysis->IsContinueBlock(bb.id())) {
       // |bb| must be an empty block ending with a branch to the header.
       Instruction* inst = &*bb.begin();
       if (inst->opcode() != SpvOpBranch) {
@@ -805,6 +831,11 @@
           struct_cfg_analysis->ContainingLoop(bb.id())) {
         return true;
       }
+    } else if (struct_cfg_analysis->IsMergeBlock(bb.id())) {
+      // |bb| must be an empty block ending with OpUnreachable.
+      if (bb.begin()->opcode() != SpvOpUnreachable) {
+        return true;
+      }
     } else {
       return true;
     }
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index d7e18f0..f8edd27 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -240,43 +240,29 @@
   // return block at the end of the pass.
   void CreateReturnBlock();
 
-  // Creates a Phi node in |merge_block| for the result of |inst| coming from
-  // |predecessor|.  Any uses of the result of |inst| that are no longer
+  // Creates a Phi node in |merge_block| for the result of |inst|.
+  // Any uses of the result of |inst| that are no longer
   // dominated by |inst|, are replaced with the result of the new |OpPhi|
   // instruction.
-  void CreatePhiNodesForInst(BasicBlock* merge_block, uint32_t predecessor,
-                             Instruction& inst);
+  void CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst);
 
-  // Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
-  // that are needed to make the code correct.  It is assumed that at this point
-  // there are no unreachable blocks in the control flow graph.
+  // 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
+  // is assumed that at this point there are no unreachable blocks in the
+  // control flow graph.
   void AddNewPhiNodes();
 
-  // Creates any new phi nodes that are needed in |bb| now that |pred| is no
-  // longer the only block that preceedes |bb|.  |header_id| is the id of the
-  // basic block for the loop or selection construct that merges at |bb|.
-  void AddNewPhiNodes(BasicBlock* bb, BasicBlock* pred, uint32_t header_id);
+  // Creates any new phi nodes that are needed in |bb|.  |AddNewPhiNodes| must
+  // have already been called on the original dominators of |bb|.
+  void AddNewPhiNodes(BasicBlock* bb);
 
-  // Saves |block| to a list of basic block that will require OpPhi nodes to be
-  // added by calling |AddNewPhiNodes|.  It is assumed that |block| used to have
-  // a single predecessor, |single_original_pred|, but now has more.
-  void MarkForNewPhiNodes(BasicBlock* block, BasicBlock* single_original_pred);
-
-  // Return the original single predcessor of |block| if it was flagged as
-  // having a single predecessor.  |nullptr| is returned otherwise.
-  BasicBlock* MarkedSinglePred(BasicBlock* block) {
-    auto it = new_merge_nodes_.find(block);
-    if (it != new_merge_nodes_.end()) {
-      return it->second;
-    } else {
-      return nullptr;
-    }
-  }
+  // Records the terminator of immediate dominator for every basic block in
+  // |function|.
+  void RecordImmediateDominators(Function* function);
 
   // Modifies existing OpPhi instruction in |target| block to account for the
-  // new edge from |new_source|.  The value for that edge will be an Undef. If
-  // |target| only had a single predecessor, then it is marked as needing new
-  // phi nodes.  See |MarkForNewPhiNodes|.
+  // new edge from |new_source|.  The value for that edge will be an Undef.
   //
   // The CFG must not include the edge from |new_source| to |target| yet.
   void UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target);
@@ -302,6 +288,11 @@
   // |merge_target| as the merge node.
   void CreateDummyLoop(BasicBlock* merge_target);
 
+  // Returns true if |function| has an unreachable block that is not a continue
+  // target that simply branches back to the header, or a merge block containing
+  // 1 instruction which is OpUnreachable.
+  bool HasNontrivialUnreachableBlocks(Function* function);
+
   // A stack used to keep track of the innermost contain loop and selection
   // constructs.
   std::vector<StructuredControlState> state_;
@@ -325,12 +316,19 @@
   // after processing the current function.
   BasicBlock* final_return_block_;
 
-  // This map contains the set of nodes that use to have a single predcessor,
-  // but now have more.  They will need new OpPhi nodes.  For each of the nodes,
-  // it is mapped to it original single predcessor.  It is assumed there are no
-  // values that will need a phi on the new edges.
-  std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
-  bool HasNontrivialUnreachableBlocks(Function* function);
+  // This is a map from a node to its original immediate dominator identified by
+  // the terminator if that block.  We use the terminator because the block we
+  // want may change if the block is split.
+  std::unordered_map<BasicBlock*, Instruction*> original_dominator_;
+
+  // A map from a basic block, bb, to the set of basic blocks which represent
+  // the new edges that reach |bb|.
+  std::unordered_map<BasicBlock*, std::set<uint32_t>> new_edges_;
+
+  // Contains all return blocks that are merged. This is set is populated while
+  // processing structured blocks and used to properly construct OpPhi
+  // instructions.
+  std::unordered_set<uint32_t> return_blocks_;
 };
 
 }  // namespace opt
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 30e80d7..5c1e6ca 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -21,7 +21,6 @@
 #include <vector>
 
 #include <source/spirv_optimizer_options.h>
-#include "code_sink.h"
 #include "source/opt/build_module.h"
 #include "source/opt/log.h"
 #include "source/opt/pass_manager.h"
@@ -57,8 +56,8 @@
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  spv_target_env target_env;        // Target environment.
-  opt::PassManager pass_manager;    // Internal implementation pass manager.
+  spv_target_env target_env;      // Target environment.
+  opt::PassManager pass_manager;  // Internal implementation pass manager.
 };
 
 Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@@ -116,6 +115,10 @@
           // Make private variable function scope
           .RegisterPass(CreateEliminateDeadFunctionsPass())
           .RegisterPass(CreatePrivateToLocalPass())
+          // Fix up the storage classes that DXC may have purposely generated
+          // incorrectly.  All functions are inlined, and a lot of dead code has
+          // been removed.
+          .RegisterPass(CreateFixStorageClassPass())
           // Propagate the value stored to the loads in very simple cases.
           .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
           .RegisterPass(CreateLocalSingleStoreElimPass())
@@ -182,10 +185,7 @@
       .RegisterPass(CreateRedundancyEliminationPass())
       .RegisterPass(CreateDeadBranchElimPass())
       .RegisterPass(CreateBlockMergePass())
-      .RegisterPass(CreateSimplificationPass())
-      .RegisterPass(CreateCodeSinkingPass());
-  // Currently exposing driver bugs resulting in crashes (#946)
-  // .RegisterPass(CreateCommonUniformElimPass())
+      .RegisterPass(CreateSimplificationPass());
 }
 
 Optimizer& Optimizer::RegisterSizePasses() {
@@ -213,14 +213,25 @@
       .RegisterPass(CreateDeadInsertElimPass())
       .RegisterPass(CreateRedundancyEliminationPass())
       .RegisterPass(CreateCFGCleanupPass())
-      // Currently exposing driver bugs resulting in crashes (#946)
-      // .RegisterPass(CreateCommonUniformElimPass())
       .RegisterPass(CreateAggressiveDCEPass());
 }
 
-Optimizer& Optimizer::RegisterWebGPUPasses() {
-  return RegisterPass(CreateAggressiveDCEPass())
-      .RegisterPass(CreateDeadBranchElimPass());
+Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
+  return RegisterPass(CreateStripDebugInfoPass())
+      .RegisterPass(CreateStripAtomicCounterMemoryPass())
+      .RegisterPass(CreateGenerateWebGPUInitializersPass())
+      .RegisterPass(CreateLegalizeVectorShufflePass())
+      .RegisterPass(CreateSplitInvalidUnreachablePass())
+      .RegisterPass(CreateEliminateDeadConstantPass())
+      .RegisterPass(CreateFlattenDecorationPass())
+      .RegisterPass(CreateAggressiveDCEPass())
+      .RegisterPass(CreateDeadBranchElimPass())
+      .RegisterPass(CreateCompactIdsPass());
+}
+
+Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() {
+  return RegisterPass(CreateDecomposeInitializedVariablesPass())
+      .RegisterPass(CreateCompactIdsPass());
 }
 
 bool Optimizer::RegisterPassesFromFlags(const std::vector<std::string>& flags) {
@@ -265,7 +276,9 @@
   //
   // Both Pass::name() and Pass::desc() should be static class members so they
   // can be invoked without creating a pass instance.
-  if (pass_name == "strip-debug") {
+  if (pass_name == "strip-atomic-counter-memory") {
+    RegisterPass(CreateStripAtomicCounterMemoryPass());
+  } else if (pass_name == "strip-debug") {
     RegisterPass(CreateStripDebugInfoPass());
   } else if (pass_name == "strip-reflect") {
     RegisterPass(CreateStripReflectInfoPass());
@@ -323,14 +336,14 @@
     RegisterPass(CreateEliminateDeadFunctionsPass());
   } else if (pass_name == "eliminate-local-multi-store") {
     RegisterPass(CreateLocalMultiStoreElimPass());
-  } else if (pass_name == "eliminate-common-uniform") {
-    RegisterPass(CreateCommonUniformElimPass());
   } else if (pass_name == "eliminate-dead-const") {
     RegisterPass(CreateEliminateDeadConstantPass());
   } else if (pass_name == "eliminate-dead-inserts") {
     RegisterPass(CreateDeadInsertElimPass());
   } else if (pass_name == "eliminate-dead-variables") {
     RegisterPass(CreateDeadVariableEliminationPass());
+  } else if (pass_name == "eliminate-dead-members") {
+    RegisterPass(CreateEliminateDeadMembersPass());
   } else if (pass_name == "fold-spec-const-op-composite") {
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
   } else if (pass_name == "loop-unswitch") {
@@ -380,7 +393,7 @@
   } else if (pass_name == "replace-invalid-opcode") {
     RegisterPass(CreateReplaceInvalidOpcodePass());
   } else if (pass_name == "inst-bindless-check") {
-    RegisterPass(CreateInstBindlessCheckPass(7, 23));
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 1));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
@@ -443,12 +456,22 @@
     RegisterPass(CreateCCPPass());
   } else if (pass_name == "code-sink") {
     RegisterPass(CreateCodeSinkingPass());
+  } else if (pass_name == "fix-storage-class") {
+    RegisterPass(CreateFixStorageClassPass());
   } else if (pass_name == "O") {
     RegisterPerformancePasses();
   } else if (pass_name == "Os") {
     RegisterSizePasses();
   } else if (pass_name == "legalize-hlsl") {
     RegisterLegalizationPasses();
+  } else if (pass_name == "generate-webgpu-initializers") {
+    RegisterPass(CreateGenerateWebGPUInitializersPass());
+  } else if (pass_name == "legalize-vector-shuffle") {
+    RegisterPass(CreateLegalizeVectorShufflePass());
+  } else if (pass_name == "split-invalid-unreachable") {
+    RegisterPass(CreateLegalizeVectorShufflePass());
+  } else if (pass_name == "decompose-initialized-variables") {
+    RegisterPass(CreateDecomposeInitializedVariablesPass());
   } else {
     Errorf(consumer(), nullptr, {},
            "Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -499,12 +522,28 @@
   if (context == nullptr) return false;
 
   context->set_max_id_bound(opt_options->max_id_bound_);
+  context->set_preserve_bindings(opt_options->preserve_bindings_);
+  context->set_preserve_spec_constants(opt_options->preserve_spec_constants_);
 
+  impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
+  impl_->pass_manager.SetTargetEnv(impl_->target_env);
   auto status = impl_->pass_manager.Run(context.get());
-  if (status == opt::Pass::Status::SuccessWithChange ||
-      (status == opt::Pass::Status::SuccessWithoutChange &&
-       (optimized_binary->data() != original_binary ||
-        optimized_binary->size() != original_binary_size))) {
+
+  bool binary_changed = false;
+  if (status == opt::Pass::Status::SuccessWithChange) {
+    binary_changed = true;
+  } else if (status == opt::Pass::Status::SuccessWithoutChange) {
+    if (optimized_binary->size() != original_binary_size ||
+        (memcmp(optimized_binary->data(), original_binary,
+                original_binary_size) != 0)) {
+      binary_changed = true;
+      Log(consumer(), SPV_MSG_WARNING, nullptr, {},
+          "Binary unexpectedly changed despite optimizer saying there was no "
+          "change");
+    }
+  }
+
+  if (binary_changed) {
     optimized_binary->clear();
     context->module()->ToBinary(optimized_binary, /* skip_nop = */ true);
   }
@@ -522,10 +561,20 @@
   return *this;
 }
 
+Optimizer& Optimizer::SetValidateAfterAll(bool validate) {
+  impl_->pass_manager.SetValidateAfterAll(validate);
+  return *this;
+}
+
 Optimizer::PassToken CreateNullPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
 }
 
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::StripAtomicCounterMemoryPass>());
+}
+
 Optimizer::PassToken CreateStripDebugInfoPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::StripDebugInfoPass>());
@@ -541,6 +590,11 @@
       MakeUnique<opt::EliminateDeadFunctionsPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
@@ -653,11 +707,6 @@
       MakeUnique<opt::ProcessLinesPass>(opt::kLinesEliminateDeadLines));
 }
 
-Optimizer::PassToken CreateCommonUniformElimPass() {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::CommonUniformElimPass>());
-}
-
 Optimizer::PassToken CreateCompactIdsPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::CompactIdsPass>());
@@ -789,9 +838,14 @@
 }
 
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id) {
+                                                 uint32_t shader_id,
+                                                 bool input_length_enable,
+                                                 bool input_init_enable,
+                                                 uint32_t version) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id,
+                                             input_length_enable,
+                                             input_init_enable, version));
 }
 
 Optimizer::PassToken CreateCodeSinkingPass() {
@@ -799,4 +853,29 @@
       MakeUnique<opt::CodeSinkingPass>());
 }
 
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::GenerateWebGPUInitializersPass>());
+}
+
+Optimizer::PassToken CreateFixStorageClassPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::FixStorageClass>());
+}
+
+Optimizer::PassToken CreateLegalizeVectorShufflePass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::LegalizeVectorShufflePass>());
+}
+
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::DecomposeInitializedVariablesPass>());
+}
+
+Optimizer::PassToken CreateSplitInvalidUnreachablePass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::SplitInvalidUnreachablePass>());
+}
+
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index edcd245..a783f4f 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -16,6 +16,7 @@
 
 #include "source/opt/pass.h"
 
+#include "source/opt/ir_builder.h"
 #include "source/opt/iterator.h"
 
 namespace spvtools {
@@ -52,5 +53,72 @@
   return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
 }
 
+uint32_t Pass::GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
+                            Instruction* insertion_position) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  uint32_t original_type_id = object_to_copy->type_id();
+  if (original_type_id == new_type_id) {
+    return object_to_copy->result_id();
+  }
+
+  InstructionBuilder ir_builder(
+      context(), insertion_position,
+      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
+
+  analysis::Type* original_type = type_mgr->GetType(original_type_id);
+  analysis::Type* new_type = type_mgr->GetType(new_type_id);
+
+  if (const analysis::Array* original_array_type = original_type->AsArray()) {
+    uint32_t original_element_type_id =
+        type_mgr->GetId(original_array_type->element_type());
+
+    analysis::Array* new_array_type = new_type->AsArray();
+    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
+    uint32_t new_element_type_id =
+        type_mgr->GetId(new_array_type->element_type());
+
+    std::vector<uint32_t> element_ids;
+    const analysis::Constant* length_const =
+        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
+    assert(length_const->AsIntConstant());
+    uint32_t array_length = length_const->AsIntConstant()->GetU32();
+    for (uint32_t i = 0; i < array_length; i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          original_element_type_id, object_to_copy->result_id(), {i});
+      element_ids.push_back(
+          GenerateCopy(extract, new_element_type_id, insertion_position));
+    }
+
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else if (const analysis::Struct* original_struct_type =
+                 original_type->AsStruct()) {
+    analysis::Struct* new_struct_type = new_type->AsStruct();
+
+    const std::vector<const analysis::Type*>& original_types =
+        original_struct_type->element_types();
+    const std::vector<const analysis::Type*>& new_types =
+        new_struct_type->element_types();
+    std::vector<uint32_t> element_ids;
+    for (uint32_t i = 0; i < original_types.size(); i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          type_mgr->GetId(original_types[i]), object_to_copy->result_id(), {i});
+      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
+                                         insertion_position));
+    }
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else {
+    // If we do not have an aggregate type, then we have a problem.  Either we
+    // found multiple instances of the same type, or we are copying to an
+    // incompatible type.  Either way the code is illegal.
+    assert(false &&
+           "Don't know how to copy this type.  Code is likely illegal.");
+  }
+  return 0;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/pass.h b/source/opt/pass.h
index c95f502..0667c3d 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -125,6 +125,12 @@
   // TODO(1841): Handle id overflow.
   uint32_t TakeNextId() { return context_->TakeNextId(); }
 
+  // Returns the id whose value is the same as |object_to_copy| except its type
+  // is |new_type_id|.  Any instructions needed to generate this value will be
+  // inserted before |insertion_position|.
+  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
+                        Instruction* insertion_position);
+
  private:
   MessageConsumer consumer_;  // Message consumer.
 
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index fa1e1d8..be53d34 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -51,6 +51,20 @@
     if (one_status == Pass::Status::Failure) return one_status;
     if (one_status == Pass::Status::SuccessWithChange) status = one_status;
 
+    if (validate_after_all_) {
+      spvtools::SpirvTools tools(target_env_);
+      tools.SetMessageConsumer(consumer());
+      std::vector<uint32_t> binary;
+      context->module()->ToBinary(&binary, true);
+      if (!tools.Validate(binary.data(), binary.size(), val_options_)) {
+        std::string msg = "Validation failed after pass ";
+        msg += pass->name();
+        spv_position_t null_pos{0, 0, 0};
+        consumer()(SPV_MSG_INTERNAL_ERROR, "", null_pos, msg.c_str());
+        return Pass::Status::Failure;
+      }
+    }
+
     // Reset the pass to free any memory used by the pass.
     pass.reset(nullptr);
   }
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index ed88aa1..9686ddd 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -43,7 +43,10 @@
   PassManager()
       : consumer_(nullptr),
         print_all_stream_(nullptr),
-        time_report_stream_(nullptr) {}
+        time_report_stream_(nullptr),
+        target_env_(SPV_ENV_UNIVERSAL_1_2),
+        val_options_(nullptr),
+        validate_after_all_(false) {}
 
   // Sets the message consumer to the given |consumer|.
   void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
@@ -89,6 +92,24 @@
     return *this;
   }
 
+  // Sets the target environment for validation.
+  PassManager& SetTargetEnv(spv_target_env env) {
+    target_env_ = env;
+    return *this;
+  }
+
+  // Sets the validation options.
+  PassManager& SetValidatorOptions(spv_validator_options options) {
+    val_options_ = options;
+    return *this;
+  }
+
+  // Sets the option to validate after each pass.
+  PassManager& SetValidateAfterAll(bool validate) {
+    validate_after_all_ = validate;
+    return *this;
+  }
+
  private:
   // Consumer for messages.
   MessageConsumer consumer_;
@@ -100,6 +121,12 @@
   // The output stream to write the resource utilization of each pass. If this
   // is null, no output is generated.
   std::ostream* time_report_stream_;
+  // The target environment.
+  spv_target_env target_env_;
+  // The validator options (used when validating each pass).
+  spv_validator_options val_options_;
+  // Controls whether validation occurs after every pass.
+  bool validate_after_all_;
 };
 
 inline void PassManager::AddPass(std::unique_ptr<Pass> pass) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index f7b675e..0a348e4 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -23,21 +23,25 @@
 #include "source/opt/cfg_cleanup_pass.h"
 #include "source/opt/code_sink.h"
 #include "source/opt/combine_access_chains.h"
-#include "source/opt/common_uniform_elim_pass.h"
 #include "source/opt/compact_ids_pass.h"
 #include "source/opt/copy_prop_arrays.h"
 #include "source/opt/dead_branch_elim_pass.h"
 #include "source/opt/dead_insert_elim_pass.h"
 #include "source/opt/dead_variable_elimination.h"
+#include "source/opt/decompose_initialized_variables_pass.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_members_pass.h"
+#include "source/opt/fix_storage_class.h"
 #include "source/opt/flatten_decoration_pass.h"
 #include "source/opt/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"
+#include "source/opt/generate_webgpu_initializers_pass.h"
 #include "source/opt/if_conversion.h"
 #include "source/opt/inline_exhaustive_pass.h"
 #include "source/opt/inline_opaque_pass.h"
 #include "source/opt/inst_bindless_check_pass.h"
+#include "source/opt/legalize_vector_shuffle_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
 #include "source/opt/local_redundancy_elimination.h"
@@ -60,8 +64,10 @@
 #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/split_invalid_unreachable_pass.h"
 #include "source/opt/ssa_rewrite_pass.h"
 #include "source/opt/strength_reduction_pass.h"
+#include "source/opt/strip_atomic_counter_memory_pass.h"
 #include "source/opt/strip_debug_info_pass.h"
 #include "source/opt/strip_reflect_info_pass.h"
 #include "source/opt/unify_const_pass.h"
diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp
index 02909a7..d41d8f2 100644
--- a/source/opt/private_to_local_pass.cpp
+++ b/source/opt/private_to_local_pass.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -38,6 +39,7 @@
     return Status::SuccessWithoutChange;
 
   std::vector<std::pair<Instruction*, Function*>> variables_to_move;
+  std::unordered_set<uint32_t> localized_variables;
   for (auto& inst : context()->types_values()) {
     if (inst.opcode() != SpvOpVariable) {
       continue;
@@ -57,6 +59,27 @@
   modified = !variables_to_move.empty();
   for (auto p : variables_to_move) {
     MoveVariable(p.first, p.second);
+    localized_variables.insert(p.first->result_id());
+  }
+
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // In SPIR-V 1.4 and later entry points must list private storage class
+    // variables that are statically used by the entry point. Go through the
+    // entry points and remove any references to variables that were localized.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        // Execution model, function id and name are always kept.
+        if (i < 3 ||
+            !localized_variables.count(entry.GetSingleWordInOperand(i))) {
+          new_operands.push_back(entry.GetInOperand(i));
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        context()->AnalyzeUses(&entry);
+      }
+    }
   }
 
   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
@@ -165,6 +188,7 @@
       UpdateUses(inst->result_id());
       break;
     case SpvOpName:
+    case SpvOpEntryPoint:  // entry points will be updated separately.
       break;
     default:
       assert(spvOpcodeIsDecoration(inst->opcode()) &&
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 79d90bd..8106442 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -45,7 +45,8 @@
 inline bool IsTypeInst(SpvOp opcode) {
   return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
          opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
-         opcode == SpvOpTypeAccelerationStructureNV;
+         opcode == SpvOpTypeAccelerationStructureNV ||
+         opcode == SpvOpTypeCooperativeMatrixNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
   return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/remove_duplicates_pass.cpp b/source/opt/remove_duplicates_pass.cpp
index a37e9df..0e65cc8 100644
--- a/source/opt/remove_duplicates_pass.cpp
+++ b/source/opt/remove_duplicates_pass.cpp
@@ -96,35 +96,67 @@
     return modified;
   }
 
+  analysis::TypeManager type_manager(context()->consumer(), context());
+
   std::vector<Instruction*> visited_types;
+  std::vector<analysis::ForwardPointer> visited_forward_pointers;
   std::vector<Instruction*> to_delete;
   for (auto* i = &*context()->types_values_begin(); i; i = i->NextNode()) {
+    const bool is_i_forward_pointer = i->opcode() == SpvOpTypeForwardPointer;
+
     // We only care about types.
-    if (!spvOpcodeGeneratesType((i->opcode())) &&
-        i->opcode() != SpvOpTypeForwardPointer) {
+    if (!spvOpcodeGeneratesType(i->opcode()) && !is_i_forward_pointer) {
       continue;
     }
 
-    // Is the current type equal to one of the types we have aready visited?
-    SpvId id_to_keep = 0u;
-    // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
-    // ResultIdTrie from unify_const_pass.cpp for this.
-    for (auto j : visited_types) {
-      if (AreTypesEqual(*i, *j, context())) {
-        id_to_keep = j->result_id();
-        break;
+    if (!is_i_forward_pointer) {
+      // Is the current type equal to one of the types we have already visited?
+      SpvId id_to_keep = 0u;
+      analysis::Type* i_type = type_manager.GetType(i->result_id());
+      assert(i_type);
+      // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
+      // ResultIdTrie from unify_const_pass.cpp for this.
+      for (auto j : visited_types) {
+        analysis::Type* j_type = type_manager.GetType(j->result_id());
+        assert(j_type);
+        if (*i_type == *j_type) {
+          id_to_keep = j->result_id();
+          break;
+        }
       }
-    }
 
-    if (id_to_keep == 0u) {
-      // This is a never seen before type, keep it around.
-      visited_types.emplace_back(i);
+      if (id_to_keep == 0u) {
+        // This is a never seen before type, keep it around.
+        visited_types.emplace_back(i);
+      } else {
+        // The same type has already been seen before, remove this one.
+        context()->KillNamesAndDecorates(i->result_id());
+        context()->ReplaceAllUsesWith(i->result_id(), id_to_keep);
+        modified = true;
+        to_delete.emplace_back(i);
+      }
     } else {
-      // The same type has already been seen before, remove this one.
-      context()->KillNamesAndDecorates(i->result_id());
-      context()->ReplaceAllUsesWith(i->result_id(), id_to_keep);
-      modified = true;
-      to_delete.emplace_back(i);
+      analysis::ForwardPointer i_type(
+          i->GetSingleWordInOperand(0u),
+          (SpvStorageClass)i->GetSingleWordInOperand(1u));
+      i_type.SetTargetPointer(
+          type_manager.GetType(i_type.target_id())->AsPointer());
+
+      // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
+      // ResultIdTrie from unify_const_pass.cpp for this.
+      const bool found_a_match =
+          std::find(std::begin(visited_forward_pointers),
+                    std::end(visited_forward_pointers),
+                    i_type) != std::end(visited_forward_pointers);
+
+      if (!found_a_match) {
+        // This is a never seen before type, keep it around.
+        visited_forward_pointers.emplace_back(i_type);
+      } else {
+        // The same type has already been seen before, remove this one.
+        modified = true;
+        to_delete.emplace_back(i);
+      }
     }
   }
 
@@ -151,8 +183,8 @@
 
   analysis::DecorationManager decoration_manager(context()->module());
   for (auto* i = &*context()->annotation_begin(); i;) {
-    // Is the current decoration equal to one of the decorations we have aready
-    // visited?
+    // Is the current decoration equal to one of the decorations we have
+    // already visited?
     bool already_visited = false;
     // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
     // ResultIdTrie from unify_const_pass.cpp for this.
@@ -177,20 +209,5 @@
   return modified;
 }
 
-bool RemoveDuplicatesPass::AreTypesEqual(const Instruction& inst1,
-                                         const Instruction& inst2,
-                                         IRContext* context) {
-  if (inst1.opcode() != inst2.opcode()) return false;
-  if (!IsTypeInst(inst1.opcode())) return false;
-
-  const analysis::Type* type1 =
-      context->get_type_mgr()->GetType(inst1.result_id());
-  const analysis::Type* type2 =
-      context->get_type_mgr()->GetType(inst2.result_id());
-  if (type1 && type2 && *type1 == *type2) return true;
-
-  return false;
-}
-
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/remove_duplicates_pass.h b/source/opt/remove_duplicates_pass.h
index 8554a98..038caa8 100644
--- a/source/opt/remove_duplicates_pass.h
+++ b/source/opt/remove_duplicates_pass.h
@@ -36,12 +36,6 @@
   const char* name() const override { return "remove-duplicates"; }
   Status Process() override;
 
-  // TODO(pierremoreau): Move this function somewhere else (e.g. pass.h or
-  // within the type manager)
-  // Returns whether two types are equal, and have the same decorations.
-  static bool AreTypesEqual(const Instruction& inst1, const Instruction& inst2,
-                            IRContext* context);
-
  private:
   // Remove duplicate capabilities from the module
   //
diff --git a/source/opt/scalar_analysis_nodes.h b/source/opt/scalar_analysis_nodes.h
index 450522e..b0e3fef 100644
--- a/source/opt/scalar_analysis_nodes.h
+++ b/source/opt/scalar_analysis_nodes.h
@@ -171,16 +171,17 @@
   bool IsCantCompute() const { return GetType() == CanNotCompute; }
 
 // Implements a casting method for each type.
+// clang-format off
 #define DeclareCastMethod(target)                  \
   virtual target* As##target() { return nullptr; } \
   virtual const target* As##target() const { return nullptr; }
-  DeclareCastMethod(SEConstantNode);
-  DeclareCastMethod(SERecurrentNode);
-  DeclareCastMethod(SEAddNode);
-  DeclareCastMethod(SEMultiplyNode);
-  DeclareCastMethod(SENegative);
-  DeclareCastMethod(SEValueUnknown);
-  DeclareCastMethod(SECantCompute);
+  DeclareCastMethod(SEConstantNode)
+  DeclareCastMethod(SERecurrentNode)
+  DeclareCastMethod(SEAddNode)
+  DeclareCastMethod(SEMultiplyNode)
+  DeclareCastMethod(SENegative)
+  DeclareCastMethod(SEValueUnknown)
+  DeclareCastMethod(SECantCompute)
 #undef DeclareCastMethod
 
   // Get the analysis which has this node in its cache.
@@ -200,6 +201,7 @@
   // The number of nodes created.
   static uint32_t NumberOfNodes;
 };
+// clang-format on
 
 // Function object to handle the hashing of SENodes. Hashing algorithm hashes
 // the type (as a string), the literal value of any constants, and the child
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 2b2397d..9ae1ae8 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -60,23 +60,25 @@
     Instruction* varInst = worklist.front();
     worklist.pop();
 
-    if (!ReplaceVariable(varInst, &worklist))
-      return Status::Failure;
-    else
-      status = Status::SuccessWithChange;
+    Status var_status = ReplaceVariable(varInst, &worklist);
+    if (var_status == Status::Failure)
+      return var_status;
+    else if (var_status == Status::SuccessWithChange)
+      status = var_status;
   }
 
   return status;
 }
 
-bool ScalarReplacementPass::ReplaceVariable(
+Pass::Status ScalarReplacementPass::ReplaceVariable(
     Instruction* inst, std::queue<Instruction*>* worklist) {
   std::vector<Instruction*> replacements;
-  CreateReplacementVariables(inst, &replacements);
+  if (!CreateReplacementVariables(inst, &replacements)) {
+    return Status::Failure;
+  }
 
   std::vector<Instruction*> dead;
-  dead.push_back(inst);
-  if (!get_def_use_mgr()->WhileEachUser(
+  if (get_def_use_mgr()->WhileEachUser(
           inst, [this, &replacements, &dead](Instruction* user) {
             if (!IsAnnotationInst(user->opcode())) {
               switch (user->opcode()) {
@@ -90,8 +92,10 @@
                   break;
                 case SpvOpAccessChain:
                 case SpvOpInBoundsAccessChain:
-                  if (!ReplaceAccessChain(user, replacements)) return false;
-                  dead.push_back(user);
+                  if (ReplaceAccessChain(user, replacements))
+                    dead.push_back(user);
+                  else
+                    return false;
                   break;
                 case SpvOpName:
                 case SpvOpMemberName:
@@ -103,7 +107,10 @@
             }
             return true;
           }))
-    return false;
+    dead.push_back(inst);
+
+  // If there are no dead instructions to clean up, return with no changes.
+  if (dead.empty()) return Status::SuccessWithoutChange;
 
   // Clean up some dead code.
   while (!dead.empty()) {
@@ -123,7 +130,7 @@
     }
   }
 
-  return true;
+  return Status::SuccessWithChange;
 }
 
 void ScalarReplacementPass::ReplaceWholeLoad(
@@ -225,12 +232,13 @@
   // indexes) or a direct use of the replacement variable.
   uint32_t indexId = chain->GetSingleWordInOperand(1u);
   const Instruction* index = get_def_use_mgr()->GetDef(indexId);
-  size_t indexValue = GetConstantInteger(index);
-  if (indexValue > replacements.size()) {
-    // Out of bounds access, this is illegal IR.
+  uint64_t indexValue = GetConstantInteger(index);
+  if (indexValue >= replacements.size()) {
+    // Out of bounds access, this is illegal IR.  Notice that OpAccessChain
+    // indexing is 0-based, so we should also reject index == size-of-array.
     return false;
   } else {
-    const Instruction* var = replacements[indexValue];
+    const Instruction* var = replacements[static_cast<size_t>(indexValue)];
     if (chain->NumInOperands() > 2) {
       // Replace input access chain with another access chain.
       BasicBlock::iterator chainIter(chain);
@@ -257,7 +265,7 @@
   return true;
 }
 
-void ScalarReplacementPass::CreateReplacementVariables(
+bool ScalarReplacementPass::CreateReplacementVariables(
     Instruction* inst, std::vector<Instruction*>* replacements) {
   Instruction* type = GetStorageType(inst);
 
@@ -302,6 +310,8 @@
   }
 
   TransferAnnotations(inst, replacements);
+  return std::find(replacements->begin(), replacements->end(), nullptr) ==
+         replacements->end();
 }
 
 void ScalarReplacementPass::TransferAnnotations(
@@ -457,16 +467,16 @@
   }
 }
 
-size_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
+uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
   assert(op.words.size() <= 2);
-  size_t len = 0;
+  uint64_t len = 0;
   for (uint32_t i = 0; i != op.words.size(); ++i) {
     len |= (op.words[i] << (32 * i));
   }
   return len;
 }
 
-size_t ScalarReplacementPass::GetConstantInteger(
+uint64_t ScalarReplacementPass::GetConstantInteger(
     const Instruction* constant) const {
   assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() ==
          SpvOpTypeInt);
@@ -480,7 +490,7 @@
   return GetIntegerLiteral(op);
 }
 
-size_t ScalarReplacementPass::GetArrayLength(
+uint64_t ScalarReplacementPass::GetArrayLength(
     const Instruction* arrayType) const {
   assert(arrayType->opcode() == SpvOpTypeArray);
   const Instruction* length =
@@ -488,14 +498,14 @@
   return GetConstantInteger(length);
 }
 
-size_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
+uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
   assert(type->opcode() == SpvOpTypeVector ||
          type->opcode() == SpvOpTypeMatrix);
   const Operand& op = type->GetInOperand(1u);
   assert(op.words.size() <= 2);
-  size_t len = 0;
-  for (uint32_t i = 0; i != op.words.size(); ++i) {
-    len |= (op.words[i] << (32 * i));
+  uint64_t len = 0;
+  for (size_t i = 0; i != op.words.size(); ++i) {
+    len |= (static_cast<uint64_t>(op.words[i]) << (32ull * i));
   }
   return len;
 }
@@ -717,7 +727,7 @@
     return false;
   return true;
 }
-bool ScalarReplacementPass::IsLargerThanSizeLimit(size_t length) const {
+bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
   if (max_num_elements_ == 0) {
     return false;
   }
@@ -801,7 +811,9 @@
   const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
   Instruction* null_inst =
       const_mgr->GetDefiningInstruction(null_const, type_id);
-  context()->UpdateDefUse(null_inst);
+  if (null_inst != nullptr) {
+    context()->UpdateDefUse(null_inst);
+  }
   return null_inst;
 }
 
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index f5d7612..3a17045 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -117,9 +117,12 @@
   // for element of the composite type. Uses of |inst| are updated as
   // appropriate. If the replacement variables are themselves scalarizable, they
   // get added to |worklist| for further processing. If any replacement
-  // variable ends up with no uses it is erased. Returns false if any
-  // subsequent access chain is out of bounds.
-  bool ReplaceVariable(Instruction* inst, std::queue<Instruction*>* worklist);
+  // variable ends up with no uses it is erased. Returns
+  //  - Status::SuccessWithoutChange if the variable could not be replaced.
+  //  - Status::SuccessWithChange if it made replacements.
+  //  - Status::Failure if it couldn't create replacement variables.
+  Pass::Status ReplaceVariable(Instruction* inst,
+                               std::queue<Instruction*>* worklist);
 
   // Returns the underlying storage type for |inst|.
   //
@@ -145,30 +148,31 @@
                       std::vector<Instruction*>* replacements);
 
   // Populates |replacements| with a new OpVariable for each element of |inst|.
+  // Returns true if the replacement variables were successfully created.
   //
   // |inst| must be an OpVariable of a composite type. New variables are
   // initialized the same as the corresponding index in |inst|. |replacements|
   // will contain a variable for each element of the composite with matching
   // indexes (i.e. the 0'th element of |inst| is the 0'th entry of
   // |replacements|).
-  void CreateReplacementVariables(Instruction* inst,
+  bool CreateReplacementVariables(Instruction* inst,
                                   std::vector<Instruction*>* replacements);
 
   // Returns the value of an OpConstant of integer type.
   //
   // |constant| must use two or fewer words to generate the value.
-  size_t GetConstantInteger(const Instruction* constant) const;
+  uint64_t GetConstantInteger(const Instruction* constant) const;
 
   // Returns the integer literal for |op|.
-  size_t GetIntegerLiteral(const Operand& op) const;
+  uint64_t GetIntegerLiteral(const Operand& op) const;
 
   // Returns the array length for |arrayInst|.
-  size_t GetArrayLength(const Instruction* arrayInst) const;
+  uint64_t GetArrayLength(const Instruction* arrayInst) const;
 
   // Returns the number of elements in |type|.
   //
   // |type| must be a vector or matrix type.
-  size_t GetNumElements(const Instruction* type) const;
+  uint64_t GetNumElements(const Instruction* type) const;
 
   // Returns true if |id| is a specialization constant.
   //
@@ -217,6 +221,7 @@
 
   // 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);
 
   // Maps storage type to a pointer type enclosing that type.
@@ -228,7 +233,7 @@
   // Limit on the number of members in an object that will be replaced.
   // 0 means there is no limit.
   uint32_t max_num_elements_;
-  bool IsLargerThanSizeLimit(size_t length) const;
+  bool IsLargerThanSizeLimit(uint64_t length) const;
   char name_[55];
 };
 
diff --git a/source/opt/simplification_pass.cpp b/source/opt/simplification_pass.cpp
index 5fbafbd..6ea4566 100644
--- a/source/opt/simplification_pass.cpp
+++ b/source/opt/simplification_pass.cpp
@@ -55,8 +55,12 @@
             process_phis.insert(inst);
           }
 
-          if (inst->opcode() == SpvOpCopyObject ||
-              folder.FoldInstruction(inst)) {
+          bool is_foldable_copy =
+              inst->opcode() == SpvOpCopyObject &&
+              context()->get_decoration_mgr()->HaveSubsetOfDecorations(
+                  inst->result_id(), inst->GetSingleWordInOperand(0));
+
+          if (is_foldable_copy || folder.FoldInstruction(inst)) {
             modified = true;
             context()->AnalyzeUses(inst);
             get_def_use_mgr()->ForEachUser(inst, [&work_list, &process_phis,
@@ -85,7 +89,13 @@
   for (size_t i = 0; i < work_list.size(); ++i) {
     Instruction* inst = work_list[i];
     in_work_list.erase(inst);
-    if (inst->opcode() == SpvOpCopyObject || folder.FoldInstruction(inst)) {
+
+    bool is_foldable_copy =
+        inst->opcode() == SpvOpCopyObject &&
+        context()->get_decoration_mgr()->HaveSubsetOfDecorations(
+            inst->result_id(), inst->GetSingleWordInOperand(0));
+
+    if (is_foldable_copy || folder.FoldInstruction(inst)) {
       modified = true;
       context()->AnalyzeUses(inst);
       get_def_use_mgr()->ForEachUser(
diff --git a/source/opt/split_invalid_unreachable_pass.cpp b/source/opt/split_invalid_unreachable_pass.cpp
new file mode 100644
index 0000000..31cfbc3
--- /dev/null
+++ b/source/opt/split_invalid_unreachable_pass.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/split_invalid_unreachable_pass.h"
+
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status SplitInvalidUnreachablePass::Process() {
+  bool changed = false;
+  std::unordered_set<uint32_t> entry_points;
+  for (auto entry_point : context()->module()->entry_points()) {
+    entry_points.insert(entry_point.GetSingleWordOperand(1));
+  }
+
+  for (auto func = context()->module()->begin();
+       func != context()->module()->end(); ++func) {
+    if (entry_points.find(func->result_id()) == entry_points.end()) continue;
+    std::unordered_set<uint32_t> continue_targets;
+    std::unordered_set<uint32_t> merge_blocks;
+    std::unordered_set<BasicBlock*> unreachable_blocks;
+    for (auto block = func->begin(); block != func->end(); ++block) {
+      unreachable_blocks.insert(&*block);
+      uint32_t continue_target = block->ContinueBlockIdIfAny();
+      if (continue_target != 0) continue_targets.insert(continue_target);
+      uint32_t merge_block = block->MergeBlockIdIfAny();
+      if (merge_block != 0) merge_blocks.insert(merge_block);
+    }
+
+    cfg()->ForEachBlockInPostOrder(
+        func->entry().get(), [&unreachable_blocks](BasicBlock* inner_block) {
+          unreachable_blocks.erase(inner_block);
+        });
+
+    for (auto unreachable : unreachable_blocks) {
+      uint32_t block_id = unreachable->id();
+      if (continue_targets.find(block_id) == continue_targets.end() ||
+          merge_blocks.find(block_id) == merge_blocks.end()) {
+        continue;
+      }
+
+      std::vector<std::tuple<Instruction*, uint32_t>> usages;
+      context()->get_def_use_mgr()->ForEachUse(
+          unreachable->GetLabelInst(),
+          [&usages](Instruction* use, uint32_t idx) {
+            if ((use->opcode() == SpvOpLoopMerge && idx == 0) ||
+                use->opcode() == SpvOpSelectionMerge) {
+              usages.push_back(std::make_pair(use, idx));
+            }
+          });
+
+      for (auto usage : usages) {
+        Instruction* use;
+        uint32_t idx;
+        std::tie(use, idx) = usage;
+        uint32_t new_id = context()->TakeNextId();
+        std::unique_ptr<Instruction> new_label(
+            new Instruction(context(), SpvOpLabel, 0, new_id, {}));
+        get_def_use_mgr()->AnalyzeInstDefUse(new_label.get());
+        std::unique_ptr<BasicBlock> new_block(
+            new BasicBlock(std::move(new_label)));
+        auto* block_ptr = new_block.get();
+        InstructionBuilder builder(context(), new_block.get(),
+                                   IRContext::kAnalysisDefUse |
+                                       IRContext::kAnalysisInstrToBlockMapping);
+        builder.AddUnreachable();
+        cfg()->RegisterBlock(block_ptr);
+        (&*func)->InsertBasicBlockBefore(std::move(new_block), unreachable);
+        use->SetInOperand(0, {new_id});
+        get_def_use_mgr()->UpdateDefUse(use);
+        cfg()->AddEdges(block_ptr);
+        changed = true;
+      }
+    }
+  }
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/split_invalid_unreachable_pass.h b/source/opt/split_invalid_unreachable_pass.h
new file mode 100644
index 0000000..a561344
--- /dev/null
+++ b/source/opt/split_invalid_unreachable_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_
+#define SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Attempts to legalize for WebGPU by splitting up invalid unreachable blocks.
+// Specifically, looking for cases of unreachable merge-blocks and
+// continue-targets that are used more then once, which is illegal in WebGPU.
+class SplitInvalidUnreachablePass : public Pass {
+ public:
+  const char* name() const override { return "split-invalid-unreachable"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index f2cb2da..7144ca0 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -102,6 +102,7 @@
                                       uint32_t repl_id) {
   for (uint32_t user_id : phi_to_remove.users()) {
     PhiCandidate* user_phi = GetPhiCandidate(user_id);
+    BasicBlock* bb = pass_->context()->get_instr_block(user_id);
     if (user_phi) {
       // If the user is a Phi candidate, replace all arguments that refer to
       // |phi_to_remove.result_id()| with |repl_id|.
@@ -110,6 +111,10 @@
           arg = repl_id;
         }
       }
+    } else if (bb->id() == user_id) {
+      // The phi candidate is the definition of the variable at basic block
+      // |bb|.  We must change this to the replacement.
+      WriteVariable(phi_to_remove.var_id(), bb, repl_id);
     } else {
       // For regular loads, traverse the |load_replacement_| table looking for
       // instances of |phi_to_remove|.
@@ -259,6 +264,8 @@
     // require a Phi instruction.  This will act as |var_id|'s current
     // definition to break potential cycles.
     PhiCandidate& phi_candidate = CreatePhiCandidate(var_id, bb);
+
+    // Set the value for |bb| to avoid an infinite recursion.
     WriteVariable(var_id, bb, phi_candidate.result_id());
     val_id = AddPhiOperands(&phi_candidate);
   }
diff --git a/source/opt/ssa_rewrite_pass.h b/source/opt/ssa_rewrite_pass.h
index c0373dc..fddbdaf 100644
--- a/source/opt/ssa_rewrite_pass.h
+++ b/source/opt/ssa_rewrite_pass.h
@@ -188,6 +188,9 @@
   // value |val_id|.
   void WriteVariable(uint32_t var_id, BasicBlock* bb, uint32_t val_id) {
     defs_at_block_[bb][var_id] = val_id;
+    if (auto* pc = GetPhiCandidate(val_id)) {
+      pc->AddUser(bb->id());
+    }
   }
 
   // Processes the store operation |inst| in basic block |bb|. This extracts
diff --git a/source/opt/strip_atomic_counter_memory_pass.cpp b/source/opt/strip_atomic_counter_memory_pass.cpp
new file mode 100644
index 0000000..47714b7
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/strip_atomic_counter_memory_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StripAtomicCounterMemoryPass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([this, &changed](Instruction* inst) {
+    auto indices = spvOpcodeMemorySemanticsOperandIndices(inst->opcode());
+    if (indices.empty()) return;
+
+    for (auto idx : indices) {
+      auto mem_sem_id = inst->GetSingleWordOperand(idx);
+      const auto& mem_sem_inst =
+          context()->get_def_use_mgr()->GetDef(mem_sem_id);
+      // The spec explicitly says that this id must be an OpConstant
+      auto mem_sem_val = mem_sem_inst->GetSingleWordOperand(2);
+      if (!(mem_sem_val & SpvMemorySemanticsAtomicCounterMemoryMask)) {
+        continue;
+      }
+      mem_sem_val &= ~SpvMemorySemanticsAtomicCounterMemoryMask;
+
+      analysis::Integer int_type(32, false);
+      const analysis::Type* uint32_type =
+          context()->get_type_mgr()->GetRegisteredType(&int_type);
+      auto* new_const = context()->get_constant_mgr()->GetConstant(
+          uint32_type, {mem_sem_val});
+      auto* new_const_inst =
+          context()->get_constant_mgr()->GetDefiningInstruction(new_const);
+      auto new_const_id = new_const_inst->result_id();
+
+      inst->SetOperand(idx, {new_const_id});
+      context()->UpdateDefUse(inst);
+      changed = true;
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/strip_atomic_counter_memory_pass.h b/source/opt/strip_atomic_counter_memory_pass.h
new file mode 100644
index 0000000..62e274a
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+#define SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Removes the AtomicCounterMemory bit from the value being passed into memory
+// semantics. This bit being set is ignored in Vulkan environments and
+// forbidden WebGPU ones.
+class StripAtomicCounterMemoryPass : public Pass {
+ public:
+  const char* name() const override { return "strip-atomic-counter-memory"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp
index d78ec56..152ded5 100644
--- a/source/opt/struct_cfg_analysis.cpp
+++ b/source/opt/struct_cfg_analysis.cpp
@@ -19,7 +19,7 @@
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
-}
+}  // namespace
 
 namespace spvtools {
 namespace opt {
@@ -37,6 +37,8 @@
 }
 
 void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) {
+  if (func->begin() == func->end()) return;
+
   std::list<BasicBlock*> order;
   context_->cfg()->ComputeStructuredOrder(func, &*func->begin(), &order);
 
@@ -50,6 +52,7 @@
   state.emplace_back();
   state[0].cinfo.containing_construct = 0;
   state[0].cinfo.containing_loop = 0;
+  state[0].cinfo.containing_switch = 0;
   state[0].merge_node = 0;
 
   for (BasicBlock* block : order) {
@@ -72,8 +75,15 @@
 
       if (merge_inst->opcode() == SpvOpLoopMerge) {
         new_state.cinfo.containing_loop = block->id();
+        new_state.cinfo.containing_switch = 0;
       } else {
         new_state.cinfo.containing_loop = state.back().cinfo.containing_loop;
+        if (merge_inst->NextNode()->opcode() == SpvOpSwitch) {
+          new_state.cinfo.containing_switch = block->id();
+        } else {
+          new_state.cinfo.containing_switch =
+              state.back().cinfo.containing_switch;
+        }
       }
 
       state.emplace_back(new_state);
@@ -82,6 +92,11 @@
   }
 }
 
+uint32_t StructuredCFGAnalysis::ContainingConstruct(Instruction* inst) {
+  uint32_t bb = context_->get_instr_block(inst)->id();
+  return ContainingConstruct(bb);
+}
+
 uint32_t StructuredCFGAnalysis::MergeBlock(uint32_t bb_id) {
   uint32_t header_id = ContainingConstruct(bb_id);
   if (header_id == 0) {
@@ -115,6 +130,17 @@
   return merge_inst->GetSingleWordInOperand(kContinueNodeIndex);
 }
 
+uint32_t StructuredCFGAnalysis::SwitchMergeBlock(uint32_t bb_id) {
+  uint32_t header_id = ContainingSwitch(bb_id);
+  if (header_id == 0) {
+    return 0;
+  }
+
+  BasicBlock* header = context_->cfg()->block(header_id);
+  Instruction* merge_inst = header->GetMergeInst();
+  return merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
+}
+
 bool StructuredCFGAnalysis::IsContinueBlock(uint32_t bb_id) {
   assert(bb_id != 0);
   return LoopContinueBlock(bb_id) == bb_id;
diff --git a/source/opt/struct_cfg_analysis.h b/source/opt/struct_cfg_analysis.h
index ef0229d..f25266a 100644
--- a/source/opt/struct_cfg_analysis.h
+++ b/source/opt/struct_cfg_analysis.h
@@ -42,6 +42,11 @@
     return it->second.containing_construct;
   }
 
+  // Returns the id of the header of the innermost merge construct
+  // that contains |inst|.  Returns |0| if |inst| is not contained in any
+  // merge construct.
+  uint32_t ContainingConstruct(Instruction* inst);
+
   // Returns the id of the merge block of the innermost merge construct
   // that contains |bb_id|.  Returns |0| if |bb_id| is not contained in any
   // merge construct.
@@ -68,6 +73,21 @@
   // construct.
   uint32_t LoopContinueBlock(uint32_t bb_id);
 
+  // Returns the id of the header of the innermost switch construct
+  // that contains |bb_id| as long as there is no intervening loop.  Returns |0|
+  // if no such construct exists.
+  uint32_t ContainingSwitch(uint32_t bb_id) {
+    auto it = bb_to_construct_.find(bb_id);
+    if (it == bb_to_construct_.end()) {
+      return 0;
+    }
+    return it->second.containing_switch;
+  }
+  // Returns the id of the merge block of the innermost switch construct
+  // that contains |bb_id| as long as there is no intervening loop.  Return |0|
+  // if no such block exists.
+  uint32_t SwitchMergeBlock(uint32_t bb_id);
+
   bool IsContinueBlock(uint32_t bb_id);
   bool IsMergeBlock(uint32_t bb_id);
 
@@ -82,6 +102,7 @@
   struct ConstructInfo {
     uint32_t containing_construct;
     uint32_t containing_loop;
+    uint32_t containing_switch;
   };
 
   // Populates |bb_to_construct_| with the innermost containing merge and loop
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 001883c..1c27b16 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -66,7 +66,13 @@
 }
 
 void TypeManager::AnalyzeTypes(const Module& module) {
-  // First pass through the types.  Any types that reference a forward pointer
+  // First pass through the constants, as some will be needed when traversing
+  // the types in the next pass.
+  for (const auto* inst : module.GetConstants()) {
+    id_to_constant_inst_[inst->result_id()] = inst;
+  }
+
+  // Then pass through the types.  Any types that reference a forward pointer
   // (directly or indirectly) are incomplete, and are added to incomplete types.
   for (const auto* inst : module.GetTypes()) {
     RecordIfTypeDefinition(*inst);
@@ -154,7 +160,7 @@
 
 #ifndef NDEBUG
   // Check if the type pool contains two types that are the same.  This
-  // is an indication that the hashing and comparision are wrong.  It
+  // is an indication that the hashing and comparison are wrong.  It
   // will cause a problem if the type pool gets resized and everything
   // is rehashed.
   for (auto& i : type_pool_) {
@@ -504,9 +510,8 @@
     }
     case Type::kArray: {
       const Array* array_ty = type.AsArray();
-      const Type* ele_ty = array_ty->element_type();
       rebuilt_ty =
-          MakeUnique<Array>(RebuildType(*ele_ty), array_ty->LengthId());
+          MakeUnique<Array>(array_ty->element_type(), array_ty->length_info());
       break;
     }
     case Type::kRuntimeArray: {
@@ -636,15 +641,56 @@
     case SpvOpTypeSampledImage:
       type = new SampledImage(GetType(inst.GetSingleWordInOperand(0)));
       break;
-    case SpvOpTypeArray:
-      type = new Array(GetType(inst.GetSingleWordInOperand(0)),
-                       inst.GetSingleWordInOperand(1));
+    case SpvOpTypeArray: {
+      const uint32_t length_id = inst.GetSingleWordInOperand(1);
+      const Instruction* length_constant_inst = id_to_constant_inst_[length_id];
+      assert(length_constant_inst);
+
+      // How will we distinguish one length value from another?
+      // Determine extra words required to distinguish this array length
+      // from another.
+      std::vector<uint32_t> extra_words{Array::LengthInfo::kDefiningId};
+      // If it is a specialised constant, retrieve its SpecId.
+      // Only OpSpecConstant has a SpecId.
+      uint32_t spec_id = 0u;
+      bool has_spec_id = false;
+      if (length_constant_inst->opcode() == SpvOpSpecConstant) {
+        context()->get_decoration_mgr()->ForEachDecoration(
+            length_id, SpvDecorationSpecId,
+            [&spec_id, &has_spec_id](const Instruction& decoration) {
+              assert(decoration.opcode() == SpvOpDecorate);
+              spec_id = decoration.GetSingleWordOperand(2u);
+              has_spec_id = true;
+            });
+      }
+      const auto opcode = length_constant_inst->opcode();
+      if (has_spec_id) {
+        extra_words.push_back(spec_id);
+      }
+      if ((opcode == SpvOpConstant) || (opcode == SpvOpSpecConstant)) {
+        // Always include the literal constant words.  In the spec constant
+        // case, the constant might not be overridden, so it's still
+        // significant.
+        extra_words.insert(extra_words.end(),
+                           length_constant_inst->GetOperand(2).words.begin(),
+                           length_constant_inst->GetOperand(2).words.end());
+        extra_words[0] = has_spec_id ? Array::LengthInfo::kConstantWithSpecId
+                                     : Array::LengthInfo::kConstant;
+      } else {
+        assert(extra_words[0] == Array::LengthInfo::kDefiningId);
+        extra_words.push_back(length_id);
+      }
+      assert(extra_words.size() >= 2);
+      Array::LengthInfo length_info{length_id, extra_words};
+
+      type = new Array(GetType(inst.GetSingleWordInOperand(0)), length_info);
+
       if (id_to_incomplete_type_.count(inst.GetSingleWordInOperand(0))) {
         incomplete_types_.emplace_back(inst.result_id(), type);
         id_to_incomplete_type_[inst.result_id()] = type;
         return type;
       }
-      break;
+    } break;
     case SpvOpTypeRuntimeArray:
       type = new RuntimeArray(GetType(inst.GetSingleWordInOperand(0)));
       if (id_to_incomplete_type_.count(inst.GetSingleWordInOperand(0))) {
diff --git a/source/opt/type_manager.h b/source/opt/type_manager.h
index c44969e..ecc7858 100644
--- a/source/opt/type_manager.h
+++ b/source/opt/type_manager.h
@@ -209,6 +209,8 @@
 
   IdToTypeMap id_to_incomplete_type_;  // Maps ids to their type representations
                                        // for incomplete types.
+
+  std::unordered_map<uint32_t, const Instruction*> id_to_constant_inst_;
 };
 
 }  // namespace analysis
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index cfafc7d..3717fd1 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -12,14 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/opt/types.h"
+
 #include <algorithm>
 #include <cassert>
 #include <cstdint>
 #include <sstream>
+#include <string>
 #include <unordered_set>
 
-#include "source/opt/types.h"
 #include "source/util/make_unique.h"
+#include "spirv/unified1/spirv.h"
 
 namespace spvtools {
 namespace opt {
@@ -99,7 +102,7 @@
 #define DeclareKindCase(kind)                   \
   case k##kind:                                 \
     type = MakeUnique<kind>(*this->As##kind()); \
-    break;
+    break
     DeclareKindCase(Void);
     DeclareKindCase(Bool);
     DeclareKindCase(Integer);
@@ -143,7 +146,7 @@
   switch (kind_) {
 #define DeclareKindCase(kind) \
   case k##kind:               \
-    return As##kind()->IsSame(&other);
+    return As##kind()->IsSame(&other)
     DeclareKindCase(Void);
     DeclareKindCase(Bool);
     DeclareKindCase(Integer);
@@ -192,7 +195,7 @@
 #define DeclareKindCase(type)                   \
   case k##type:                                 \
     As##type()->GetExtraHashWords(words, seen); \
-    break;
+    break
     DeclareKindCase(Void);
     DeclareKindCase(Bool);
     DeclareKindCase(Integer);
@@ -383,29 +386,42 @@
   image_type_->GetHashWords(words, seen);
 }
 
-Array::Array(Type* type, uint32_t length_id)
-    : Type(kArray), element_type_(type), length_id_(length_id) {
+Array::Array(const Type* type, const Array::LengthInfo& length_info_arg)
+    : Type(kArray), element_type_(type), length_info_(length_info_arg) {
+  assert(type != nullptr);
   assert(!type->AsVoid());
+  // We always have a word to say which case we're in, followed
+  // by at least one more word.
+  assert(length_info_arg.words.size() >= 2);
 }
 
 bool Array::IsSameImpl(const Type* that, IsSameCache* seen) const {
   const Array* at = that->AsArray();
   if (!at) return false;
-  return length_id_ == at->length_id_ &&
-         element_type_->IsSameImpl(at->element_type_, seen) &&
-         HasSameDecorations(that);
+  bool is_same = element_type_->IsSameImpl(at->element_type_, seen);
+  is_same = is_same && HasSameDecorations(that);
+  is_same = is_same && (length_info_.words == at->length_info_.words);
+  return is_same;
 }
 
 std::string Array::str() const {
   std::ostringstream oss;
-  oss << "[" << element_type_->str() << ", id(" << length_id_ << ")]";
+  oss << "[" << element_type_->str() << ", id(" << LengthId() << "), words(";
+  const char* spacer = "";
+  for (auto w : length_info_.words) {
+    oss << spacer << w;
+    spacer = ",";
+  }
+  oss << ")]";
   return oss.str();
 }
 
 void Array::GetExtraHashWords(std::vector<uint32_t>* words,
                               std::unordered_set<const Type*>* seen) const {
   element_type_->GetHashWords(words, seen);
-  words->push_back(length_id_);
+  // This should mirror the logic in IsSameImpl
+  words->insert(words->end(), length_info_.words.begin(),
+                length_info_.words.end());
 }
 
 void Array::ReplaceElementType(const Type* type) { element_type_ = type; }
@@ -540,7 +556,12 @@
   return HasSameDecorations(that);
 }
 
-std::string Pointer::str() const { return pointee_type_->str() + "*"; }
+std::string Pointer::str() const {
+  std::ostringstream os;
+  os << pointee_type_->str() << " " << static_cast<uint32_t>(storage_class_)
+     << "*";
+  return os.str();
+}
 
 void Pointer::GetExtraHashWords(std::vector<uint32_t>* words,
                                 std::unordered_set<const Type*>* seen) const {
@@ -609,7 +630,8 @@
 bool ForwardPointer::IsSameImpl(const Type* that, IsSameCache*) const {
   const ForwardPointer* fpt = that->AsForwardPointer();
   if (!fpt) return false;
-  return target_id_ == fpt->target_id_ &&
+  return (pointer_ && fpt->pointer_ ? *pointer_ == *fpt->pointer_
+                                    : target_id_ == fpt->target_id_) &&
          storage_class_ == fpt->storage_class_ && HasSameDecorations(that);
 }
 
diff --git a/source/opt/types.h b/source/opt/types.h
index fe0f39a..c997b1f 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -27,6 +27,7 @@
 #include <vector>
 
 #include "source/latest_version_spirv_header.h"
+#include "source/opt/instruction.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
@@ -144,37 +145,6 @@
   // TODO(alanbaker): Update this if variable pointers become a core feature.
   bool IsUniqueType(bool allowVariablePointers = false) const;
 
-// A bunch of methods for casting this type to a given type. Returns this if the
-// cast can be done, nullptr otherwise.
-#define DeclareCastMethod(target)                  \
-  virtual target* As##target() { return nullptr; } \
-  virtual const target* As##target() const { return nullptr; }
-  DeclareCastMethod(Void);
-  DeclareCastMethod(Bool);
-  DeclareCastMethod(Integer);
-  DeclareCastMethod(Float);
-  DeclareCastMethod(Vector);
-  DeclareCastMethod(Matrix);
-  DeclareCastMethod(Image);
-  DeclareCastMethod(Sampler);
-  DeclareCastMethod(SampledImage);
-  DeclareCastMethod(Array);
-  DeclareCastMethod(RuntimeArray);
-  DeclareCastMethod(Struct);
-  DeclareCastMethod(Opaque);
-  DeclareCastMethod(Pointer);
-  DeclareCastMethod(Function);
-  DeclareCastMethod(Event);
-  DeclareCastMethod(DeviceEvent);
-  DeclareCastMethod(ReserveId);
-  DeclareCastMethod(Queue);
-  DeclareCastMethod(Pipe);
-  DeclareCastMethod(ForwardPointer);
-  DeclareCastMethod(PipeStorage);
-  DeclareCastMethod(NamedBarrier);
-  DeclareCastMethod(AccelerationStructureNV);
-#undef DeclareCastMethod
-
   bool operator==(const Type& other) const;
 
   // Returns the hash value of this type.
@@ -196,6 +166,38 @@
       std::vector<uint32_t>* words,
       std::unordered_set<const Type*>* pSet) const = 0;
 
+// A bunch of methods for casting this type to a given type. Returns this if the
+// cast can be done, nullptr otherwise.
+// clang-format off
+#define DeclareCastMethod(target)                  \
+  virtual target* As##target() { return nullptr; } \
+  virtual const target* As##target() const { return nullptr; }
+  DeclareCastMethod(Void)
+  DeclareCastMethod(Bool)
+  DeclareCastMethod(Integer)
+  DeclareCastMethod(Float)
+  DeclareCastMethod(Vector)
+  DeclareCastMethod(Matrix)
+  DeclareCastMethod(Image)
+  DeclareCastMethod(Sampler)
+  DeclareCastMethod(SampledImage)
+  DeclareCastMethod(Array)
+  DeclareCastMethod(RuntimeArray)
+  DeclareCastMethod(Struct)
+  DeclareCastMethod(Opaque)
+  DeclareCastMethod(Pointer)
+  DeclareCastMethod(Function)
+  DeclareCastMethod(Event)
+  DeclareCastMethod(DeviceEvent)
+  DeclareCastMethod(ReserveId)
+  DeclareCastMethod(Queue)
+  DeclareCastMethod(Pipe)
+  DeclareCastMethod(ForwardPointer)
+  DeclareCastMethod(PipeStorage)
+  DeclareCastMethod(NamedBarrier)
+  DeclareCastMethod(AccelerationStructureNV)
+#undef DeclareCastMethod
+
  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,
@@ -209,6 +211,7 @@
 
   Kind kind_;
 };
+// clang-format on
 
 class Integer : public Type {
  public:
@@ -356,12 +359,36 @@
 
 class Array : public Type {
  public:
-  Array(Type* element_type, uint32_t length_id);
+  // Data about the length operand, that helps us distinguish between one
+  // array length and another.
+  struct LengthInfo {
+    // The result id of the instruction defining the length.
+    const uint32_t id;
+    enum Case : uint32_t {
+      kConstant = 0,
+      kConstantWithSpecId = 1,
+      kDefiningId = 2
+    };
+    // Extra words used to distinshish one array length and another.
+    //  - if OpConstant, then it's 0, then the words in the literal constant
+    //    value.
+    //  - if OpSpecConstant, then it's 1, then the SpecID decoration if there
+    //    is one, followed by the words in the literal constant value.
+    //    The spec might not be overridden, in which case we'll end up using
+    //    the literal value.
+    //  - Otherwise, it's an OpSpecConsant, and this 2, then the ID (again).
+    const std::vector<uint32_t> words;
+  };
+
+  // Constructs an array type with given element and length.  If the length
+  // is an OpSpecConstant, then |spec_id| should be its SpecId decoration.
+  Array(const Type* element_type, const LengthInfo& length_info_arg);
   Array(const Array&) = default;
 
   std::string str() const override;
   const Type* element_type() const { return element_type_; }
-  uint32_t LengthId() const { return length_id_; }
+  uint32_t LengthId() const { return length_info_.id; }
+  const LengthInfo& length_info() const { return length_info_; }
 
   Array* AsArray() override { return this; }
   const Array* AsArray() const override { return this; }
@@ -375,7 +402,7 @@
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
 
   const Type* element_type_;
-  uint32_t length_id_;
+  const LengthInfo length_info_;
 };
 
 class RuntimeArray : public Type {
diff --git a/source/opt/upgrade_memory_model.cpp b/source/opt/upgrade_memory_model.cpp
index d8836a4..ef9f620 100644
--- a/source/opt/upgrade_memory_model.cpp
+++ b/source/opt/upgrade_memory_model.cpp
@@ -18,12 +18,19 @@
 
 #include "source/opt/ir_builder.h"
 #include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace opt {
 
 Pass::Status UpgradeMemoryModel::Process() {
+  // TODO: This pass needs changes to support cooperative matrices.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityCooperativeMatrixNV)) {
+    return Pass::Status::SuccessWithoutChange;
+  }
+
   // Only update Logical GLSL450 to Logical VulkanKHR.
   Instruction* memory_model = get_module()->GetMemoryModel();
   if (memory_model->GetSingleWordInOperand(0u) != SpvAddressingModelLogical ||
@@ -70,6 +77,7 @@
   // parameters are implicitly coherent in GLSL450.
 
   // Upgrade modf and frexp first since they generate new stores.
+  // In SPIR-V 1.4 or later, normalize OpCopyMemory* access operands.
   for (auto& func : *get_module()) {
     func.ForEachInst([this](Instruction* inst) {
       if (inst->opcode() == SpvOpExtInst) {
@@ -82,9 +90,38 @@
             UpgradeExtInst(inst);
           }
         }
+      } else if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+        if (inst->opcode() == SpvOpCopyMemory ||
+            inst->opcode() == SpvOpCopyMemorySized) {
+          uint32_t start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
+          if (inst->NumInOperands() > start_operand) {
+            auto num_access_words = MemoryAccessNumWords(
+                inst->GetSingleWordInOperand(start_operand));
+            if ((num_access_words + start_operand) == inst->NumInOperands()) {
+              // There is a single memory access operand. Duplicate it to have a
+              // separate operand for both source and target.
+              for (uint32_t i = 0; i < num_access_words; ++i) {
+                auto operand = inst->GetInOperand(start_operand + i);
+                inst->AddOperand(std::move(operand));
+              }
+            }
+          } else {
+            // Add two memory access operands.
+            inst->AddOperand(
+                {SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
+            inst->AddOperand(
+                {SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
+          }
+        }
       }
     });
   }
+
+  UpgradeMemoryAndImages();
+  UpgradeAtomics();
+}
+
+void UpgradeMemoryModel::UpgradeMemoryAndImages() {
   for (auto& func : *get_module()) {
     func.ForEachInst([this](Instruction* inst) {
       bool is_coherent = false;
@@ -93,6 +130,7 @@
       bool src_volatile = false;
       bool dst_coherent = false;
       bool dst_volatile = false;
+      uint32_t start_operand = 0u;
       SpvScope scope = SpvScopeQueueFamilyKHR;
       SpvScope src_scope = SpvScopeQueueFamilyKHR;
       SpvScope dst_scope = SpvScopeQueueFamilyKHR;
@@ -129,16 +167,23 @@
                        kMemory);
           break;
         case SpvOpCopyMemory:
-          UpgradeFlags(inst, 2u, dst_coherent, dst_volatile, kAvailability,
-                       kMemory);
-          UpgradeFlags(inst, 2u, src_coherent, src_volatile, kVisibility,
-                       kMemory);
-          break;
         case SpvOpCopyMemorySized:
-          UpgradeFlags(inst, 3u, dst_coherent, dst_volatile, kAvailability,
-                       kMemory);
-          UpgradeFlags(inst, 3u, src_coherent, src_volatile, kVisibility,
-                       kMemory);
+          start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
+          if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+            // There are guaranteed to be two memory access operands at this
+            // point so treat source and target separately.
+            uint32_t num_access_words = MemoryAccessNumWords(
+                inst->GetSingleWordInOperand(start_operand));
+            UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
+                         kAvailability, kMemory);
+            UpgradeFlags(inst, start_operand + num_access_words, src_coherent,
+                         src_volatile, kVisibility, kMemory);
+          } else {
+            UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
+                         kAvailability, kMemory);
+            UpgradeFlags(inst, start_operand, src_coherent, src_volatile,
+                         kVisibility, kMemory);
+          }
           break;
         case SpvOpImageRead:
         case SpvOpImageSparseRead:
@@ -158,21 +203,98 @@
         inst->AddOperand(
             {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(scope)}});
       }
-      // According to SPV_KHR_vulkan_memory_model, if both available and
-      // visible flags are used the first scope operand is for availability
-      // (writes) and the second is for visibility (reads).
-      if (dst_coherent) {
-        inst->AddOperand(
-            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
-      }
-      if (src_coherent) {
-        inst->AddOperand(
-            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+      if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+        // There are two memory access operands. The first is for the target and
+        // the second is for the source.
+        if (dst_coherent || src_coherent) {
+          start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
+          std::vector<Operand> new_operands;
+          uint32_t num_access_words =
+              MemoryAccessNumWords(inst->GetSingleWordInOperand(start_operand));
+          // The flags were already updated so subtract if we're adding a
+          // scope.
+          if (dst_coherent) --num_access_words;
+          for (uint32_t i = 0; i < start_operand + num_access_words; ++i) {
+            new_operands.push_back(inst->GetInOperand(i));
+          }
+          // Add the target scope if necessary.
+          if (dst_coherent) {
+            new_operands.push_back(
+                {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
+          }
+          // Copy the remaining current operands.
+          for (uint32_t i = start_operand + num_access_words;
+               i < inst->NumInOperands(); ++i) {
+            new_operands.push_back(inst->GetInOperand(i));
+          }
+          // Add the source scope if necessary.
+          if (src_coherent) {
+            new_operands.push_back(
+                {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+          }
+          inst->SetInOperands(std::move(new_operands));
+        }
+      } else {
+        // According to SPV_KHR_vulkan_memory_model, if both available and
+        // visible flags are used the first scope operand is for availability
+        // (writes) and the second is for visibility (reads).
+        if (dst_coherent) {
+          inst->AddOperand(
+              {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
+        }
+        if (src_coherent) {
+          inst->AddOperand(
+              {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+        }
       }
     });
   }
 }
 
+void UpgradeMemoryModel::UpgradeAtomics() {
+  for (auto& func : *get_module()) {
+    func.ForEachInst([this](Instruction* inst) {
+      if (spvOpcodeIsAtomicOp(inst->opcode())) {
+        bool unused_coherent = false;
+        bool is_volatile = false;
+        SpvScope unused_scope = SpvScopeQueueFamilyKHR;
+        std::tie(unused_coherent, is_volatile, unused_scope) =
+            GetInstructionAttributes(inst->GetSingleWordInOperand(0));
+
+        UpgradeSemantics(inst, 2u, is_volatile);
+        if (inst->opcode() == SpvOpAtomicCompareExchange ||
+            inst->opcode() == SpvOpAtomicCompareExchangeWeak) {
+          UpgradeSemantics(inst, 3u, is_volatile);
+        }
+      }
+    });
+  }
+}
+
+void UpgradeMemoryModel::UpgradeSemantics(Instruction* inst,
+                                          uint32_t in_operand,
+                                          bool is_volatile) {
+  if (!is_volatile) return;
+
+  uint32_t semantics_id = inst->GetSingleWordInOperand(in_operand);
+  const analysis::Constant* constant =
+      context()->get_constant_mgr()->FindDeclaredConstant(semantics_id);
+  const analysis::Integer* type = constant->type()->AsInteger();
+  assert(type && type->width() == 32);
+  uint32_t value = 0;
+  if (type->IsSigned()) {
+    value = static_cast<uint32_t>(constant->GetS32());
+  } else {
+    value = constant->GetU32();
+  }
+
+  value |= SpvMemorySemanticsVolatileMask;
+  auto new_constant = context()->get_constant_mgr()->GetConstant(type, {value});
+  auto new_semantics =
+      context()->get_constant_mgr()->GetDefiningInstruction(new_constant);
+  inst->SetInOperand(in_operand, {new_semantics->result_id()});
+}
+
 std::tuple<bool, bool, SpvScope> UpgradeMemoryModel::GetInstructionAttributes(
     uint32_t id) {
   // |id| is a pointer used in a memory/image instruction. Need to determine if
@@ -636,5 +758,13 @@
   builder.AddStore(ptr_id, extract_1->result_id());
 }
 
+uint32_t UpgradeMemoryModel::MemoryAccessNumWords(uint32_t mask) {
+  uint32_t result = 1;
+  if (mask & SpvMemoryAccessAlignedMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
+  return result;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/upgrade_memory_model.h b/source/opt/upgrade_memory_model.h
index 9adc33b..f75304e 100644
--- a/source/opt/upgrade_memory_model.h
+++ b/source/opt/upgrade_memory_model.h
@@ -15,11 +15,11 @@
 #ifndef LIBSPIRV_OPT_UPGRADE_MEMORY_MODEL_H_
 #define LIBSPIRV_OPT_UPGRADE_MEMORY_MODEL_H_
 
-#include "pass.h"
-
 #include <functional>
 #include <tuple>
 
+#include "pass.h"
+
 namespace spvtools {
 namespace opt {
 
@@ -57,12 +57,19 @@
   // capability and extension.
   void UpgradeMemoryModelInstruction();
 
-  // Upgrades memory, image and barrier instructions.
+  // Upgrades memory, image and atomic instructions.
   // Memory and image instructions convert coherent and volatile decorations
-  // into flags on the instruction. Barriers in tessellation shaders get the
-  // output storage semantic if appropriate.
+  // into flags on the instruction.
+  // Atomic memory semantics convert volatile decoration into flags on the
+  // instruction.
   void UpgradeInstructions();
 
+  // Upgrades memory and image operands for instructions that have them.
+  void UpgradeMemoryAndImages();
+
+  // Adds the volatile memory semantic if necessary.
+  void UpgradeAtomics();
+
   // Returns whether |id| is coherent and/or volatile.
   std::tuple<bool, bool, SpvScope> GetInstructionAttributes(uint32_t id);
 
@@ -95,6 +102,11 @@
                     bool is_volatile, OperationType operation_type,
                     InstructionType inst_type);
 
+  // Modifies the semantics at |in_operand| of |inst| to include the volatile
+  // bit if |is_volatile| is true.
+  void UpgradeSemantics(Instruction* inst, uint32_t in_operand,
+                        bool is_volatile);
+
   // Returns the result id for a constant for |scope|.
   uint32_t GetScopeConstant(SpvScope scope);
 
@@ -123,6 +135,10 @@
   // facilitate adding memory model flags.
   void UpgradeExtInst(Instruction* modf);
 
+  // Returns the number of words taken up by a memory access argument and its
+  // implied operands.
+  uint32_t MemoryAccessNumWords(uint32_t mask);
+
   // Caches the result of TraceInstruction. For a given result id and set of
   // indices, stores whether that combination is coherent and/or volatile.
   std::unordered_map<std::pair<uint32_t, std::vector<uint32_t>>,
diff --git a/source/opt/value_number_table.cpp b/source/opt/value_number_table.cpp
index 1bac63f..8df34ef 100644
--- a/source/opt/value_number_table.cpp
+++ b/source/opt/value_number_table.cpp
@@ -78,8 +78,12 @@
     return value;
   }
 
+  analysis::DecorationManager* dec_mgr = context()->get_decoration_mgr();
+
   // When we copy an object, the value numbers should be the same.
-  if (inst->opcode() == SpvOpCopyObject) {
+  if (inst->opcode() == SpvOpCopyObject &&
+      dec_mgr->HaveTheSameDecorations(inst->result_id(),
+                                      inst->GetSingleWordInOperand(0))) {
     value = GetValueNumber(inst->GetSingleWordInOperand(0));
     if (value != 0) {
       id_to_value_[inst->result_id()] = value;
@@ -89,7 +93,9 @@
 
   // Phi nodes are a type of copy.  If all of the inputs have the same value
   // number, then we can assign the result of the phi the same value number.
-  if (inst->opcode() == SpvOpPhi) {
+  if (inst->opcode() == SpvOpPhi &&
+      dec_mgr->HaveTheSameDecorations(inst->result_id(),
+                                      inst->GetSingleWordInOperand(0))) {
     value = GetValueNumber(inst->GetSingleWordInOperand(0));
     if (value != 0) {
       for (uint32_t op = 2; op < inst->NumInOperands(); op += 2) {
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 0a6bce9..def4d21 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -14,34 +14,59 @@
 set(SPIRV_TOOLS_REDUCE_SOURCES
         change_operand_reduction_opportunity.h
         change_operand_to_undef_reduction_opportunity.h
-        operand_to_const_reduction_pass.h
-        operand_to_undef_reduction_pass.h
-        operand_to_dominating_id_reduction_pass.h
+        merge_blocks_reduction_opportunity.h
+        merge_blocks_reduction_opportunity_finder.h
+        operand_to_const_reduction_opportunity_finder.h
+        operand_to_undef_reduction_opportunity_finder.h
+        operand_to_dominating_id_reduction_opportunity_finder.h
         reducer.h
         reduction_opportunity.h
+        reduction_opportunity_finder.h
         reduction_pass.h
         reduction_util.h
+        remove_block_reduction_opportunity.h
+        remove_block_reduction_opportunity_finder.h
         remove_instruction_reduction_opportunity.h
-        remove_opname_instruction_reduction_pass.h
-        remove_unreferenced_instruction_reduction_pass.h
+        remove_function_reduction_opportunity.h
+        remove_function_reduction_opportunity_finder.h
+        remove_opname_instruction_reduction_opportunity_finder.h
+        remove_selection_reduction_opportunity.h
+        remove_selection_reduction_opportunity_finder.h
+        remove_unreferenced_instruction_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
-        structured_loop_to_selection_reduction_pass.h
+        structured_loop_to_selection_reduction_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
+        simple_conditional_branch_to_branch_opportunity_finder.h
+        simple_conditional_branch_to_branch_reduction_opportunity.h
 
         change_operand_reduction_opportunity.cpp
         change_operand_to_undef_reduction_opportunity.cpp
-        operand_to_const_reduction_pass.cpp
-        operand_to_undef_reduction_pass.cpp
-        operand_to_dominating_id_reduction_pass.cpp
+        merge_blocks_reduction_opportunity.cpp
+        merge_blocks_reduction_opportunity_finder.cpp
+        operand_to_const_reduction_opportunity_finder.cpp
+        operand_to_undef_reduction_opportunity_finder.cpp
+        operand_to_dominating_id_reduction_opportunity_finder.cpp
         reducer.cpp
         reduction_opportunity.cpp
         reduction_pass.cpp
         reduction_util.cpp
+        remove_block_reduction_opportunity.cpp
+        remove_block_reduction_opportunity_finder.cpp
+        remove_function_reduction_opportunity.cpp
+        remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
-        remove_unreferenced_instruction_reduction_pass.cpp
-        remove_opname_instruction_reduction_pass.cpp
+        remove_selection_reduction_opportunity.cpp
+        remove_selection_reduction_opportunity_finder.cpp
+        remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+        remove_opname_instruction_reduction_opportunity_finder.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
-        structured_loop_to_selection_reduction_pass.cpp
-        )
+        structured_loop_to_selection_reduction_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+        simple_conditional_branch_to_branch_opportunity_finder.cpp
+        simple_conditional_branch_to_branch_reduction_opportunity.cpp
+)
 
 if(MSVC)
   # Enable parallel builds across four cores for this lib
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
index 5430d3e..c3f6fd7 100644
--- a/source/reduce/change_operand_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
index 7e1fc8e..18e6ca1 100644
--- a/source/reduce/change_operand_reduction_opportunity.h
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -15,22 +15,20 @@
 #ifndef SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
 // An opportunity to replace an id operand of an instruction with some other id.
 class ChangeOperandReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to replace operand |operand_index| of |inst|
   // with |new_id|.
-  ChangeOperandReductionOpportunity(Instruction* inst, uint32_t operand_index,
-                                    uint32_t new_id)
+  ChangeOperandReductionOpportunity(opt::Instruction* inst,
+                                    uint32_t operand_index, uint32_t new_id)
       : inst_(inst),
         operand_index_(operand_index),
         original_id_(inst->GetOperand(operand_index).words[0]),
@@ -43,7 +41,7 @@
   void Apply() override;
 
  private:
-  Instruction* const inst_;
+  opt::Instruction* const inst_;
   const uint32_t operand_index_;
   const uint32_t original_id_;
   const spv_operand_type_t original_type_;
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..2feca4a
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -0,0 +1,89 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Find the opportunities for redirecting all false targets before the
+  // opportunities for redirecting all true targets because the former
+  // opportunities disable the latter, and vice versa, and the efficiency of the
+  // reducer is improved by avoiding contiguous opportunities that disable one
+  // another.
+  for (bool redirect_to_true : {true, false}) {
+    // Consider every function.
+    for (auto& function : *context->module()) {
+      // Consider every block in the function.
+      for (auto& block : function) {
+        // The terminator must be SpvOpBranchConditional.
+        Instruction* terminator = block.terminator();
+        if (terminator->opcode() != SpvOpBranchConditional) {
+          continue;
+        }
+
+        uint32_t true_block_id =
+            terminator->GetSingleWordInOperand(kTrueBranchOperandIndex);
+        uint32_t false_block_id =
+            terminator->GetSingleWordInOperand(kFalseBranchOperandIndex);
+
+        // The conditional branch must not already be simplified.
+        if (true_block_id == false_block_id) {
+          continue;
+        }
+
+        // The redirected target must not be a back-edge to a structured loop
+        // header.
+        uint32_t redirected_block_id =
+            redirect_to_true ? false_block_id : true_block_id;
+        uint32_t containing_loop_header =
+            context->GetStructuredCFGAnalysis()->ContainingLoop(block.id());
+        // The structured CFG analysis does not include a loop header as part
+        // of the loop construct, but we want to include it, so handle this
+        // special case:
+        if (block.GetLoopMergeInst() != nullptr) {
+          containing_loop_header = block.id();
+        }
+        if (redirected_block_id == containing_loop_header) {
+          continue;
+        }
+
+        result.push_back(
+            MakeUnique<
+                ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
+                block.terminator(), redirect_to_true));
+      }
+    }
+  }
+  return result;
+}
+
+std::string
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::GetName() const {
+  return "ConditionalBranchToSimpleConditionalBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
new file mode 100644
index 0000000..c582a88
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to simplify conditional branches into simple
+// conditional branches (conditional branches with one target).
+class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..92c621e
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Instruction;
+
+ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction, bool redirect_to_true)
+    : conditional_branch_instruction_(conditional_branch_instruction),
+      redirect_to_true_(redirect_to_true) {}
+
+bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    PreconditionHolds() {
+  // Another opportunity may have already simplified this conditional branch,
+  // which should disable this opportunity.
+  return conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) !=
+         conditional_branch_instruction_->GetSingleWordInOperand(
+             kFalseBranchOperandIndex);
+}
+
+void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
+  uint32_t operand_to_modify =
+      redirect_to_true_ ? kFalseBranchOperandIndex : kTrueBranchOperandIndex;
+  uint32_t operand_to_copy =
+      redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
+
+  // Do the branch redirection.
+  conditional_branch_instruction_->SetInOperand(
+      operand_to_modify,
+      {conditional_branch_instruction_->GetSingleWordInOperand(
+          operand_to_copy)});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
new file mode 100644
index 0000000..421906b
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to simplify a conditional branch to a simple conditional
+// branch (a conditional branch with one target).
+class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|. If
+  // |redirect_to_true| is true, the false target will be changed to also point
+  // to the true target; otherwise, the true target will be changed to also
+  // point to the false target.
+  explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+
+  // If true, the false target will be changed to point to the true target;
+  // otherwise, the true target will be changed to point to the false target.
+  bool redirect_to_true_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp
new file mode 100644
index 0000000..42c7843
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.cpp
@@ -0,0 +1,83 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+#include "source/opt/block_merge_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+using opt::IRContext;
+
+MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
+    IRContext* context, Function* function, BasicBlock* block) {
+  // Precondition: the terminator has to be OpBranch.
+  assert(block->terminator()->opcode() == SpvOpBranch);
+  context_ = context;
+  function_ = function;
+  // Get the successor block associated with the OpBranch.
+  successor_block_ =
+      context->cfg()->block(block->terminator()->GetSingleWordInOperand(0));
+}
+
+bool MergeBlocksReductionOpportunity::PreconditionHolds() {
+  // Merge block opportunities can disable each other.
+  // Example: Given blocks: A->B->C.
+  // A is a loop header; B and C are blocks in the loop; C ends with OpReturn.
+  // There are two opportunities: B and C can be merged with their predecessors.
+  // Merge C. B now ends with OpReturn. We now just have: A->B.
+  // Merge B is now disabled, as this would lead to A, a loop header, ending
+  // with an OpReturn, which is invalid.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  return opt::blockmergeutil::CanMergeWithSuccessor(context_,
+                                                    predecessor_block);
+}
+
+void MergeBlocksReductionOpportunity::Apply() {
+  // While the original block that targeted the successor may not exist anymore
+  // (it might have been merged with another block), some block must exist that
+  // targets the successor.  Find it.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+
+  // We need an iterator pointing to the predecessor, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == predecessor_id) {
+      opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
+      // Block merging changes the control flow graph, so invalidate it.
+      context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity.h b/source/reduce/merge_blocks_reduction_opportunity.h
new file mode 100644
index 0000000..5c9180b
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to merge two blocks into one.
+class MergeBlocksReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to merge |block| with its successor, where |block|
+  // is inside |function|, and |context| is the enclosing IR context.
+  MergeBlocksReductionOpportunity(opt::IRContext* context,
+                                  opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  opt::Function* function_;
+
+  // Rather than holding on to the block that can be merged with its successor,
+  // we hold on to its successor. This is because the predecessor block might
+  // get merged with *its* predecessor, and so will no longer exist, while the
+  // successor will continue to exist until this opportunity gets applied.
+  opt::BasicBlock* successor_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..89d6263
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/opt/block_merge_util.h"
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::string MergeBlocksReductionOpportunityFinder::GetName() const {
+  return "MergeBlocksReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      // See whether it is possible to merge this block with its successor.
+      if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
+        // It is, so record an opportunity to do this.
+        result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
+            context, &function, &block));
+      }
+    }
+  }
+  return result;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h
new file mode 100644
index 0000000..dbf82fe
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to merge blocks together.
+class MergeBlocksReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  MergeBlocksReductionOpportunityFinder() = default;
+
+  ~MergeBlocksReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
similarity index 90%
rename from source/reduce/operand_to_const_reduction_pass.cpp
rename to source/reduce/operand_to_const_reduction_opportunity_finder.cpp
index 4d04506..3e0a224 100644
--- a/source/reduce/operand_to_const_reduction_pass.cpp
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
 
 #include "source/opt/instruction.h"
 #include "source/reduce/change_operand_reduction_opportunity.h"
@@ -20,11 +20,11 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToConstReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   assert(result.empty());
 
@@ -75,8 +75,8 @@
   return result;
 }
 
-std::string OperandToConstReductionPass::GetName() const {
-  return "OperandToConstReductionPass";
+std::string OperandToConstReductionOpportunityFinder::GetName() const {
+  return "OperandToConstReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_const_reduction_pass.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h
similarity index 60%
rename from source/reduce/operand_to_const_reduction_pass.h
rename to source/reduce/operand_to_const_reduction_opportunity_finder.h
index 4e7381e..93c0dcd 100644
--- a/source/reduce/operand_to_const_reduction_pass.h
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h
@@ -12,30 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
+#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
 
-#include "source/reduce/reduction_pass.h"
+#include "source/reduce/reduction_opportunity_finder.h"
 
 namespace spvtools {
 namespace reduce {
 
-// A reduction pass for replacing id operands of instructions with ids of
+// A finder for opportunities to replace id operands of instructions with ids of
 // constants.  This reduces the extent to which ids of non-constants are used,
-// paving the way for instructions that generate them to be eliminated by other
-// passes.
-class OperandToConstReductionPass : public ReductionPass {
+// paving the way for instructions that generate them to be eliminated.
+class OperandToConstReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
  public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToConstReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+  OperandToConstReductionOpportunityFinder() = default;
 
-  ~OperandToConstReductionPass() override = default;
+  ~OperandToConstReductionOpportunityFinder() override = default;
 
   std::string GetName() const final;
 
- protected:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
       opt::IRContext* context) const final;
 
@@ -45,4 +41,4 @@
 }  // namespace reduce
 }  // namespace spvtools
 
-#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
+#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
similarity index 84%
rename from source/reduce/operand_to_dominating_id_reduction_pass.cpp
rename to source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
index 9280a41..13beb89 100644
--- a/source/reduce/operand_to_dominating_id_reduction_pass.cpp
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
@@ -12,18 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "operand_to_dominating_id_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
 #include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToDominatingIdReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Go through every instruction in every block, considering it as a potential
@@ -55,11 +58,12 @@
   return result;
 }
 
-void OperandToDominatingIdReductionPass::GetOpportunitiesForDominatingInst(
-    std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-    opt::Instruction* candidate_dominator,
-    opt::Function::iterator candidate_dominator_block, opt::Function* function,
-    opt::IRContext* context) const {
+void OperandToDominatingIdReductionOpportunityFinder::
+    GetOpportunitiesForDominatingInst(
+        std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+        Instruction* candidate_dominator,
+        Function::iterator candidate_dominator_block, Function* function,
+        IRContext* context) const {
   assert(candidate_dominator->HasResultId());
   assert(candidate_dominator->type_id());
   auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -106,8 +110,8 @@
   }
 }
 
-std::string OperandToDominatingIdReductionPass::GetName() const {
-  return "OperandToDominatingIdReductionPass";
+std::string OperandToDominatingIdReductionOpportunityFinder::GetName() const {
+  return "OperandToDominatingIdReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7745ff7
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder that aims to bring to SPIR-V (and generalize) the idea from
+// human-readable languages of e.g. finding opportunities to replace an
+// expression with one of its arguments, (x + y) -> x, or with a reference to an
+// identifier that was assigned to higher up in the program.  The generalization
+// of this is to replace an id with a different id of the same type defined in
+// some dominating instruction.
+//
+// If id x is defined and then used several times, changing each use of x to
+// some dominating definition may eventually allow the statement defining x
+// to be eliminated by another pass.
+class OperandToDominatingIdReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToDominatingIdReductionOpportunityFinder() = default;
+
+  ~OperandToDominatingIdReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  void GetOpportunitiesForDominatingInst(
+      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+      opt::Instruction* dominating_instruction,
+      opt::Function::iterator candidate_dominator_block,
+      opt::Function* function, opt::IRContext* context) const;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.h b/source/reduce/operand_to_dominating_id_reduction_pass.h
deleted file mode 100644
index 36bb201..0000000
--- a/source/reduce/operand_to_dominating_id_reduction_pass.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass that aims to bring to SPIR-V (and generalize) the idea from
-// human-readable languages of e.g. replacing an expression with one of its
-// arguments, (x + y) -> x, or with a reference to an identifier that was
-// assigned to higher up in the program.  The generalization of this is to
-// replace an id with a different id of the same type defined in some
-// dominating instruction.
-//
-// If id x is defined and then used several times, changing each use of x to
-// some dominating definition may eventually allow the statement defining x
-// to be eliminated by another pass.
-class OperandToDominatingIdReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToDominatingIdReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToDominatingIdReductionPass() override = default;
-
-  std::string GetName() const final;
-
- protected:
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-  void GetOpportunitiesForDominatingInst(
-      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-      opt::Instruction* dominating_instruction,
-      opt::Function::iterator candidate_dominator_block,
-      opt::Function* function, opt::IRContext* context) const;
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_undef_reduction_pass.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
similarity index 90%
rename from source/reduce/operand_to_undef_reduction_pass.cpp
rename to source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
index e3d8a8e..579b7df 100644
--- a/source/reduce/operand_to_undef_reduction_pass.cpp
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/operand_to_undef_reduction_pass.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
 
 #include "source/opt/instruction.h"
 #include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
@@ -20,10 +20,10 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToUndefReductionPass::GetAvailableOpportunities(
+OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
     IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
@@ -86,8 +86,8 @@
   return result;
 }
 
-std::string OperandToUndefReductionPass::GetName() const {
-  return "OperandToUndefReductionPass";
+std::string OperandToUndefReductionOpportunityFinder::GetName() const {
+  return "OperandToUndefReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
new file mode 100644
index 0000000..9cdd8cd
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to replace id operands of instructions with ids of
+// undef.
+class OperandToUndefReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToUndefReductionOpportunityFinder() = default;
+
+  ~OperandToUndefReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_undef_reduction_pass.h b/source/reduce/operand_to_undef_reduction_pass.h
deleted file mode 100644
index e4ec603..0000000
--- a/source/reduce/operand_to_undef_reduction_pass.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_PASS_H_
-
-#include "source/reduce/reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for replacing id operands of instructions with ids of undef.
-class OperandToUndefReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToUndefReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToUndefReductionPass() override = default;
-
-  std::string GetName() const final;
-
- protected:
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_PASS_H_
diff --git a/source/reduce/pch_source_reduce.h b/source/reduce/pch_source_reduce.h
index 823b55a..6c0da0c 100644
--- a/source/reduce/pch_source_reduce.h
+++ b/source/reduce/pch_source_reduce.h
@@ -16,8 +16,8 @@
 #include <functional>
 #include <string>
 #include "source/reduce/change_operand_reduction_opportunity.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
 #include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/reduction_pass.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 4f4429a..a677be3 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -12,14 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/reduce/reducer.h"
+
 #include <cassert>
 #include <sstream>
 
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
 
-#include "reducer.h"
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -53,13 +64,25 @@
 
 Reducer::ReductionResultStatus Reducer::Run(
     std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
-    spv_const_reducer_options options) const {
-  std::vector<uint32_t> current_binary = binary_in;
+    spv_const_reducer_options options,
+    spv_validator_options validator_options) const {
+  std::vector<uint32_t> current_binary(std::move(binary_in));
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
 
   // Keeps track of how many reduction attempts have been tried.  Reduction
   // bails out if this reaches a given limit.
   uint32_t reductions_applied = 0;
 
+  // Initial state should be valid.
+  if (!tools.Validate(&current_binary[0], current_binary.size(),
+                      validator_options)) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Reducer::ReductionResultStatus::kInitialStateInvalid;
+  }
+
   // Initial state should be interesting.
   if (!impl_->interestingness_function(current_binary, reductions_applied)) {
     impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -93,25 +116,31 @@
       do {
         auto maybe_result = pass->TryApplyReduction(current_binary);
         if (maybe_result.empty()) {
-          // This pass did not have any impact, so move on to the next pass.
+          // For this round, the pass has no more opportunities (chunks) to
+          // apply, so move on to the next pass.
           impl_->consumer(
               SPV_MSG_INFO, nullptr, {},
               ("Pass " + pass->GetName() + " did not make a reduction step.")
                   .c_str());
           break;
         }
+        bool interesting = false;
         std::stringstream stringstream;
         reductions_applied++;
         stringstream << "Pass " << pass->GetName() << " made reduction step "
                      << reductions_applied << ".";
         impl_->consumer(SPV_MSG_INFO, nullptr, {},
                         (stringstream.str().c_str()));
-        if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+        if (!tools.Validate(&maybe_result[0], maybe_result.size(),
+                            validator_options)) {
           // The reduction step went wrong and an invalid binary was produced.
           // By design, this shouldn't happen; this is a safeguard to stop an
           // invalid binary from being regarded as interesting.
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step produced an invalid binary.");
+          if (options->fail_on_validation_error) {
+            return Reducer::ReductionResultStatus::kStateInvalid;
+          }
         } else if (impl_->interestingness_function(maybe_result,
                                                    reductions_applied)) {
           // Success!  The binary produced by this reduction step is
@@ -120,8 +149,11 @@
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step succeeded.");
           current_binary = std::move(maybe_result);
+          interesting = true;
           another_round_worthwhile = true;
         }
+        // We must call this before the next call to TryApplyReduction.
+        pass->NotifyInteresting(interesting);
         // Bail out if the reduction step limit has been reached.
       } while (!impl_->ReachedStepLimit(reductions_applied, options));
     }
@@ -140,9 +172,38 @@
   return Reducer::ReductionResultStatus::kComplete;
 }
 
+void Reducer::AddDefaultReductionPasses() {
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveOpNameInstructionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveUnreferencedInstructionReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   StructuredLoopToSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<
+          ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+}
+
 void Reducer::AddReductionPass(
-    std::unique_ptr<ReductionPass>&& reduction_pass) {
-  impl_->passes.push_back(std::move(reduction_pass));
+    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+  impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
+      impl_->target_env, std::move(finder)));
 }
 
 bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index 3a4c26c..a9b28c3 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -18,10 +18,9 @@
 #include <functional>
 #include <string>
 
+#include "source/reduce/reduction_pass.h"
 #include "spirv-tools/libspirv.hpp"
 
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -33,7 +32,12 @@
   enum ReductionResultStatus {
     kInitialStateNotInteresting,
     kReachedStepLimit,
-    kComplete
+    kComplete,
+    kInitialStateInvalid,
+
+    // Returned when the fail-on-validation-error option is set and a
+    // reduction step yields a state that fails validation.
+    kStateInvalid,
   };
 
   // The type for a function that will take a binary and return true if and
@@ -75,16 +79,20 @@
   void SetInterestingnessFunction(
       InterestingnessFunction interestingness_function);
 
-  // Adds a reduction pass to the sequence of passes that will be iterated
-  // over.
-  void AddReductionPass(std::unique_ptr<ReductionPass>&& reduction_pass);
+  // Adds all default reduction passes.
+  void AddDefaultReductionPasses();
+
+  // Adds a reduction pass based on the given finder to the sequence of passes
+  // that will be iterated over.
+  void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
 
   // Reduces the given SPIR-V module |binary_out|.
   // The reduced binary ends up in |binary_out|.
   // A status is returned.
   ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
                             std::vector<uint32_t>* binary_out,
-                            spv_const_reducer_options options) const;
+                            spv_const_reducer_options options,
+                            spv_validator_options validator_options) const;
 
  private:
   struct Impl;                  // Opaque struct for holding internal data.
diff --git a/source/reduce/reduction_opportunity.cpp b/source/reduce/reduction_opportunity.cpp
index f562678..77be784 100644
--- a/source/reduce/reduction_opportunity.cpp
+++ b/source/reduce/reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduction_opportunity.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h
new file mode 100644
index 0000000..1837484
--- /dev/null
+++ b/source/reduce/reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Abstract class for finding opportunities for reducing a SPIR-V module.
+class ReductionOpportunityFinder {
+ public:
+  ReductionOpportunityFinder() = default;
+
+  virtual ~ReductionOpportunityFinder() = default;
+
+  // Finds and returns the reduction opportunities relevant to this pass that
+  // could be applied to the given SPIR-V module.
+  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
+  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+
+  // Provides a name for the finder.
+  virtual std::string GetName() const = 0;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
index befba8b..2cb986d 100644
--- a/source/reduce/reduction_pass.cpp
+++ b/source/reduce/reduction_pass.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <algorithm>
+#include "source/reduce/reduction_pass.h"
 
-#include "reduction_pass.h"
+#include <algorithm>
 
 #include "source/opt/build_module.h"
 
@@ -34,22 +34,21 @@
   assert(context);
 
   std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
-      GetAvailableOpportunities(context.get());
+      finder_->GetAvailableOpportunities(context.get());
 
-  if (!is_initialized_) {
-    is_initialized_ = true;
-    index_ = 0;
-    granularity_ = (uint32_t)opportunities.size();
-  }
-
-  if (opportunities.empty()) {
-    granularity_ = 1;
-    return std::vector<uint32_t>();
+  // There is no point in having a granularity larger than the number of
+  // opportunities, so reduce the granularity in this case.
+  if (granularity_ > opportunities.size()) {
+    granularity_ = std::max((uint32_t)1, (uint32_t)opportunities.size());
   }
 
   assert(granularity_ > 0);
 
   if (index_ >= opportunities.size()) {
+    // We have reached the end of the available opportunities and, therefore,
+    // the end of the round for this pass, so reset the index and decrease the
+    // granularity for the next round. Return an empty vector to signal the end
+    // of the round.
     index_ = 0;
     granularity_ = std::max((uint32_t)1, granularity_ / 2);
     return std::vector<uint32_t>();
@@ -61,8 +60,6 @@
     opportunities[i]->TryToApply();
   }
 
-  index_ += granularity_;
-
   std::vector<uint32_t> result;
   context->module()->ToBinary(&result, false);
   return result;
@@ -73,14 +70,17 @@
 }
 
 bool ReductionPass::ReachedMinimumGranularity() const {
-  if (!is_initialized_) {
-    // Conceptually we can think that if the pass has not yet been initialized,
-    // it is operating at unbounded granularity.
-    return false;
-  }
   assert(granularity_ != 0);
   return granularity_ == 1;
 }
 
+std::string ReductionPass::GetName() const { return finder_->GetName(); }
+
+void ReductionPass::NotifyInteresting(bool interesting) {
+  if (!interesting) {
+    index_ += granularity_;
+  }
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
index 57e1c5f..f2d937b 100644
--- a/source/reduce/reduction_pass.h
+++ b/source/reduce/reduction_pass.h
@@ -15,10 +15,11 @@
 #ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
 #define SOURCE_REDUCE_REDUCTION_PASS_H_
 
-#include "spirv-tools/libspirv.hpp"
+#include <limits>
 
-#include "reduction_opportunity.h"
 #include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+#include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
 namespace reduce {
@@ -32,16 +33,29 @@
 // again, until the minimum granularity is reached.
 class ReductionPass {
  public:
-  // Constructs a reduction pass with a given target environment, |target_env|.
-  // Initially the pass is uninitialized.
-  explicit ReductionPass(const spv_target_env target_env)
-      : target_env_(target_env), is_initialized_(false) {}
+  // Constructs a reduction pass with a given target environment, |target_env|,
+  // and a given finder of reduction opportunities, |finder|.
+  explicit ReductionPass(const spv_target_env target_env,
+                         std::unique_ptr<ReductionOpportunityFinder> finder)
+      : target_env_(target_env),
+        finder_(std::move(finder)),
+        index_(0),
+        granularity_(std::numeric_limits<uint32_t>::max()) {}
 
-  virtual ~ReductionPass() = default;
-
-  // Applies the reduction pass to the given binary.
+  // Applies the reduction pass to the given binary by applying a "chunk" of
+  // reduction opportunities. Returns the new binary if a chunk was applied; in
+  // this case, before the next call the caller must invoke
+  // NotifyInteresting(...) to indicate whether the new binary is interesting.
+  // Returns an empty vector if there are no more chunks left to apply; in this
+  // case, the index will be reset and the granularity lowered for the next
+  // round.
   std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
 
+  // Notifies the reduction pass whether the binary returned from
+  // TryApplyReduction is interesting, so that the next call to
+  // TryApplyReduction will avoid applying the same chunk of opportunities.
+  void NotifyInteresting(bool interesting);
+
   // Sets a consumer to which relevant messages will be directed.
   void SetMessageConsumer(MessageConsumer consumer);
 
@@ -49,20 +63,14 @@
   // applied has reached a minimum.
   bool ReachedMinimumGranularity() const;
 
-  // Returns the name of the reduction pass (useful for monitoring reduction
-  // progress).
-  virtual std::string GetName() const = 0;
-
- protected:
-  // Finds and returns the reduction opportunities relevant to this pass that
-  // could be applied to the given SPIR-V module.
-  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
-  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+  // Returns the name associated with this reduction pass (based on its
+  // associated finder).
+  std::string GetName() const;
 
  private:
   const spv_target_env target_env_;
+  const std::unique_ptr<ReductionOpportunityFinder> finder_;
   MessageConsumer consumer_;
-  bool is_initialized_;
   uint32_t index_;
   uint32_t granularity_;
 };
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
index 103d63f..2b2b7e6 100644
--- a/source/reduce/reduction_util.cpp
+++ b/source/reduce/reduction_util.cpp
@@ -19,7 +19,11 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
+using opt::Instruction;
+
+const uint32_t kTrueBranchOperandIndex = 1;
+const uint32_t kFalseBranchOperandIndex = 2;
 
 uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
   for (auto& inst : context->module()->types_values()) {
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
index d8efc97..b8ffb6e 100644
--- a/source/reduce/reduction_util.h
+++ b/source/reduce/reduction_util.h
@@ -23,6 +23,9 @@
 namespace spvtools {
 namespace reduce {
 
+extern const uint32_t kTrueBranchOperandIndex;
+extern const uint32_t kFalseBranchOperandIndex;
+
 // Returns an OpUndef id from the global value list that is of the given type,
 // adding one if it does not exist.
 uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..3ad7f72
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+
+RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
+    Function* function, BasicBlock* block)
+    : function_(function), block_(block) {
+  // precondition:
+  assert(block_->begin() != block_->end() &&
+         block_->begin()->context()->get_def_use_mgr()->NumUsers(
+             block_->id()) == 0 &&
+         "RemoveBlockReductionOpportunity block must have 0 references");
+}
+
+bool RemoveBlockReductionOpportunity::PreconditionHolds() {
+  // Removing other blocks cannot disable this opportunity.
+  return true;
+}
+
+void RemoveBlockReductionOpportunity::Apply() {
+  // We need an iterator pointing to the block, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == block_->id()) {
+      bi->KillAllInsts(true);
+      bi.Erase();
+      // Block removal changes the function, but we don't use analyses, so no
+      // need to invalidate them.
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
new file mode 100644
index 0000000..4b358ab
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced block.
+// See RemoveBlockReductionOpportunityFinder.
+class RemoveBlockReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to remove |block| in |function| in |context|.
+  RemoveBlockReductionOpportunity(opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Function* function_;
+  opt::BasicBlock* block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..a3f873f
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
+
+std::string RemoveBlockReductionOpportunityFinder::GetName() const {
+  return "RemoveBlockReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto bi = function.begin(); bi != function.end(); ++bi) {
+      if (IsBlockValidOpportunity(context, function, bi)) {
+        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
+            &function, &*bi));
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
+    IRContext* context, Function& function, Function::iterator& bi) {
+  assert(bi != function.end() && "Block iterator was out of bounds");
+
+  // Don't remove first block; we don't want to end up with no blocks.
+  if (bi == function.begin()) {
+    return false;
+  }
+
+  // Don't remove blocks with references.
+  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+    return false;
+  }
+
+  // Don't remove blocks whose instructions have outside references.
+  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool RemoveBlockReductionOpportunityFinder::
+    BlockInstructionsHaveNoOutsideReferences(IRContext* context,
+                                             const Function::iterator& bi) {
+  // Get all instructions in block.
+  std::unordered_set<uint32_t> instructions_in_block;
+  for (const Instruction& instruction : *bi) {
+    instructions_in_block.insert(instruction.unique_id());
+  }
+
+  // For each instruction...
+  for (const Instruction& instruction : *bi) {
+    // For each use of the instruction...
+    bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
+        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+          // If the use is in this block, continue (return true). Otherwise, we
+          // found an outside use; return false (and stop).
+          return instructions_in_block.find(user->unique_id()) !=
+                 instructions_in_block.end();
+        });
+
+    if (!no_uses_outside_block) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..83cd04b
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove a block. The optimizer can remove dead
+// code. However, the reducer needs to be able to remove at a fine-grained
+// level.
+class RemoveBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveBlockReductionOpportunityFinder() = default;
+
+  ~RemoveBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  // Returns true if the block |bi| in function |function| is a valid
+  // opportunity according to various restrictions.
+  static bool IsBlockValidOpportunity(opt::IRContext* context,
+                                      opt::Function& function,
+                                      opt::Function::iterator& bi);
+
+  // Returns true if the instructions (definitions) in block |bi| have no
+  // references, except for references from inside the block itself.
+  static bool BlockInstructionsHaveNoOutsideReferences(
+      opt::IRContext* context, const opt::Function::iterator& bi);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_function_reduction_opportunity.cpp b/source/reduce/remove_function_reduction_opportunity.cpp
new file mode 100644
index 0000000..ecad670
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+#include "source/opt/eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
+  // Removing one function cannot influence whether another function can be
+  // removed.
+  return true;
+}
+
+void RemoveFunctionReductionOpportunity::Apply() {
+  for (opt::Module::iterator function_it = context_->module()->begin();
+       function_it != context_->module()->end(); ++function_it) {
+    if (&*function_it == function_) {
+      opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
+                                                         &function_it);
+      return;
+    }
+  }
+  assert(0 && "Function to be removed was not found.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity.h b/source/reduce/remove_function_reduction_opportunity.h
new file mode 100644
index 0000000..d8c57db
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced function.
+class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates an opportunity to remove |function| from the module represented by
+  // |context|.
+  RemoveFunctionReductionOpportunity(opt::IRContext* context,
+                                     opt::Function* function)
+      : context_(context), function_(function) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The IR context for the module under analysis.
+  opt::IRContext* context_;
+
+  // The function that can be removed.
+  opt::Function* function_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..1edb973
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  // Consider each function.
+  for (auto& function : *context->module()) {
+    if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
+      // If the function is referenced, ignore it.
+      continue;
+    }
+    result.push_back(
+        MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
+  }
+  return result;
+}
+
+std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
+  return "RemoveFunctionReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7952a22
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove unreferenced functions.
+class RemoveFunctionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveFunctionReductionOpportunityFinder() = default;
+
+  ~RemoveFunctionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
index 7b7a74e..9ca093b 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.cpp
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/opt/ir_context.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/remove_instruction_reduction_opportunity.h b/source/reduce/remove_instruction_reduction_opportunity.h
index e9f442e..07bef50 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.h
+++ b/source/reduce/remove_instruction_reduction_opportunity.h
@@ -15,19 +15,17 @@
 #ifndef SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
 // An opportunity to remove an instruction from the SPIR-V module.
 class RemoveInstructionReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to remove |inst|.
-  explicit RemoveInstructionReductionOpportunity(Instruction* inst)
+  explicit RemoveInstructionReductionOpportunity(opt::Instruction* inst)
       : inst_(inst) {}
 
   // Always returns true, as this opportunity can always be applied.
@@ -37,7 +35,7 @@
   void Apply() override;
 
  private:
-  Instruction* inst_;
+  opt::Instruction* inst_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
similarity index 72%
rename from source/reduce/remove_opname_instruction_reduction_pass.cpp
rename to source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
index bf99bc5..f687d71 100644
--- a/source/reduce/remove_opname_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_opname_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& inst : context->module()->debugs2()) {
@@ -36,8 +37,8 @@
   return result;
 }
 
-std::string RemoveOpNameInstructionReductionPass::GetName() const {
-  return "RemoveOpNameInstructionReductionPass";
+std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
+  return "RemoveOpNameInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
new file mode 100644
index 0000000..8b9fd6f
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove OpName instructions.  As well as making
+// the module smaller, removing an OpName instruction may create opportunities
+// for subsequently removing the instructions that create the ids to which the
+// OpName applies.
+class RemoveOpNameInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveOpNameInstructionReductionOpportunityFinder() = default;
+
+  ~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
deleted file mode 100644
index 2990a49..0000000
--- a/source/reduce/remove_opname_instruction_reduction_pass.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for removing OpName instructions.  As well as making the
-// module smaller, removing an OpName instruction may create opportunities
-// for subsequently removing the instructions that create the ids to which the
-// OpName applies.
-class RemoveOpNameInstructionReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~RemoveOpNameInstructionReductionPass() override = default;
-
-  std::string GetName() const final;
-
- protected:
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity.cpp b/source/reduce/remove_selection_reduction_opportunity.cpp
new file mode 100644
index 0000000..96f0147
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.cpp
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveSelectionReductionOpportunity::PreconditionHolds() { return true; }
+
+void RemoveSelectionReductionOpportunity::Apply() {
+  auto merge_instruction = header_block_->GetMergeInst();
+  merge_instruction->context()->KillInst(merge_instruction);
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity.h b/source/reduce/remove_selection_reduction_opportunity.h
new file mode 100644
index 0000000..892618e
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity for removing a selection construct by simply removing the
+// OpSelectionMerge instruction; thus, the selection must have already been
+// simplified to a point where the instruction can be trivially removed.
+class RemoveSelectionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Constructs a reduction opportunity from the selection header |block| in
+  // |function|.
+  RemoveSelectionReductionOpportunity(opt::BasicBlock* header_block)
+      : header_block_(header_block) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The header block of the selection.
+  opt::BasicBlock* header_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..45821e2
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+}  // namespace
+
+std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
+  return "RemoveSelectionReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  // Get all loop merge and continue blocks so we can check for these later.
+  std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpLoopMerge) {
+          uint32_t merge_block_id =
+              merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+          uint32_t continue_block_id =
+              merge_instruction->GetSingleWordOperand(kContinueNodeIndex);
+          merge_and_continue_blocks_from_loops.insert(merge_block_id);
+          merge_and_continue_blocks_from_loops.insert(continue_block_id);
+        }
+      }
+    }
+  }
+
+  // Return all selection headers where the OpSelectionMergeInstruction can be
+  // removed.
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpSelectionMerge) {
+          if (CanOpSelectionMergeBeRemoved(
+                  context, block, merge_instruction,
+                  merge_and_continue_blocks_from_loops)) {
+            result.push_back(
+                MakeUnique<RemoveSelectionReductionOpportunity>(&block));
+          }
+        }
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
+    IRContext* context, const BasicBlock& header_block,
+    Instruction* merge_instruction,
+    std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
+  assert(header_block.GetMergeInst() == merge_instruction &&
+         "CanOpSelectionMergeBeRemoved(...): header block and merge "
+         "instruction mismatch");
+
+  // The OpSelectionMerge instruction is needed if either of the following are
+  // true.
+  //
+  // 1. The header block has at least two (unique) successors that are not
+  // merge or continue blocks of a loop.
+  //
+  // 2. The predecessors of the merge block are "using" the merge block to avoid
+  // divergence. In other words, there exists a predecessor of the merge block
+  // that has a successor that is not the merge block of this construct and not
+  // a merge or continue block of a loop.
+
+  // 1.
+  {
+    uint32_t divergent_successor_count = 0;
+
+    std::unordered_set<uint32_t> seen_successors;
+
+    header_block.ForEachSuccessorLabel(
+        [&seen_successors, &merge_and_continue_blocks_from_loops,
+         &divergent_successor_count](uint32_t successor) {
+          // Not already seen.
+          if (seen_successors.find(successor) == seen_successors.end()) {
+            seen_successors.insert(successor);
+            // Not a loop continue or merge.
+            if (merge_and_continue_blocks_from_loops.find(successor) ==
+                merge_and_continue_blocks_from_loops.end()) {
+              ++divergent_successor_count;
+            }
+          }
+        });
+
+    if (divergent_successor_count > 1) {
+      return false;
+    }
+  }
+
+  // 2.
+  {
+    uint32_t merge_block_id =
+        merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+    for (uint32_t predecessor_block_id :
+         context->cfg()->preds(merge_block_id)) {
+      const BasicBlock* predecessor_block =
+          context->cfg()->block(predecessor_block_id);
+      assert(predecessor_block);
+      bool found_divergent_successor = false;
+      predecessor_block->ForEachSuccessorLabel(
+          [&found_divergent_successor, merge_block_id,
+           &merge_and_continue_blocks_from_loops](uint32_t successor_id) {
+            // The successor is not the merge block, nor a loop merge or
+            // continue.
+            if (successor_id != merge_block_id &&
+                merge_and_continue_blocks_from_loops.find(successor_id) ==
+                    merge_and_continue_blocks_from_loops.end()) {
+              found_divergent_successor = true;
+            }
+          });
+      if (found_divergent_successor) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..848122b
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities for removing a selection construct by simply
+// removing the OpSelectionMerge instruction; thus, the selections must have
+// already been simplified to a point where they can be trivially removed.
+class RemoveSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveSelectionReductionOpportunityFinder() = default;
+
+  ~RemoveSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+  // Returns true if the OpSelectionMerge instruction |merge_instruction| in
+  // block |header_block| can be removed.
+  static bool CanOpSelectionMergeBeRemoved(
+      opt::IRContext* context, const opt::BasicBlock& header_block,
+      opt::Instruction* merge_instruction,
+      std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
similarity index 79%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index bc4998d..8f32435 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_unreferenced_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveUnreferencedInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveUnreferencedInstructionReductionOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& function : *context->module()) {
@@ -52,8 +53,9 @@
   return result;
 }
 
-std::string RemoveUnreferencedInstructionReductionPass::GetName() const {
-  return "RemoveUnreferencedInstructionReductionPass";
+std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName()
+    const {
+  return "RemoveUnreferencedInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
similarity index 67%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.h
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
index 44a0d55..30f460b 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.h
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -12,32 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
 
-#include "reduction_pass.h"
+#include "source/reduce/reduction_opportunity_finder.h"
 
 namespace spvtools {
 namespace reduce {
 
-// A reduction pass for removing non-control-flow instructions in blocks in
-// cases where the instruction's id is not referenced.  As well as making the
+// A finder for opportunities to remove non-control-flow instructions in blocks
+// in cases where the instruction's id is not referenced.  As well as making the
 // module smaller, removing an instruction that references particular ids may
 // create opportunities for subsequently removing the instructions that
 // generated those ids.
-class RemoveUnreferencedInstructionReductionPass : public ReductionPass {
+class RemoveUnreferencedInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
  public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveUnreferencedInstructionReductionPass(
-      const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+  RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
 
-  ~RemoveUnreferencedInstructionReductionPass() override = default;
+  ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
 
   std::string GetName() const final;
 
- protected:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
       opt::IRContext* context) const final;
 
@@ -47,4 +43,4 @@
 }  // namespace reduce
 }  // namespace spvtools
 
-#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..17a5c7e
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/reduce/reduction_util.h"
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every function.
+  for (auto& function : *context->module()) {
+    // Consider every block in the function.
+    for (auto& block : function) {
+      // The terminator must be SpvOpBranchConditional.
+      Instruction* terminator = block.terminator();
+      if (terminator->opcode() != SpvOpBranchConditional) {
+        continue;
+      }
+      // It must not be a selection header, as these cannot be followed by
+      // OpBranch.
+      if (block.GetMergeInst() &&
+          block.GetMergeInst()->opcode() == SpvOpSelectionMerge) {
+        continue;
+      }
+      // The conditional branch must be simplified.
+      if (terminator->GetSingleWordInOperand(kTrueBranchOperandIndex) !=
+          terminator->GetSingleWordInOperand(kFalseBranchOperandIndex)) {
+        continue;
+      }
+
+      result.push_back(
+          MakeUnique<SimpleConditionalBranchToBranchReductionOpportunity>(
+              block.terminator()));
+    }
+  }
+  return result;
+}
+
+std::string SimpleConditionalBranchToBranchOpportunityFinder::GetName() const {
+  return "SimpleConditionalBranchToBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
new file mode 100644
index 0000000..10b9dce
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to change simple conditional branches (conditional
+// branches with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..8968b96
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+SimpleConditionalBranchToBranchReductionOpportunity::
+    SimpleConditionalBranchToBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction)
+    : conditional_branch_instruction_(conditional_branch_instruction) {}
+
+bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
+  // We find at most one opportunity per conditional branch and simplifying
+  // another branch cannot disable this opportunity.
+  return true;
+}
+
+void SimpleConditionalBranchToBranchReductionOpportunity::Apply() {
+  assert(conditional_branch_instruction_->opcode() == SpvOpBranchConditional &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "a conditional branch");
+
+  assert(conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) ==
+             conditional_branch_instruction_->GetSingleWordInOperand(
+                 kFalseBranchOperandIndex) &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "simple");
+
+  // OpBranchConditional %condition %block_id %block_id ...
+  // ->
+  // OpBranch %block_id
+
+  conditional_branch_instruction_->SetOpcode(SpvOpBranch);
+  conditional_branch_instruction_->ReplaceOperands(
+      {{SPV_OPERAND_TYPE_ID,
+        {conditional_branch_instruction_->GetSingleWordInOperand(
+            kTrueBranchOperandIndex)}}});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
new file mode 100644
index 0000000..eddb464
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to change simple conditional branches (conditional branches
+// with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|.
+  explicit SimpleConditionalBranchToBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index bf0e085..afc1298 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -21,9 +21,13 @@
 namespace spvtools {
 namespace reduce {
 
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+using opt::Operand;
+
 namespace {
 const uint32_t kMergeNodeIndex = 0;
-const uint32_t kContinueNodeIndex = 1;
 }  // namespace
 
 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
@@ -43,15 +47,11 @@
 
   // (1) Redirect edges that point to the loop's continue target to their
   // closest merge block.
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kContinueNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
 
   // (2) Redirect edges that point to the loop's merge block to their closest
   // merge block (which might be that of an enclosing selection, for instance).
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kMergeNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
 
   // (3) Turn the loop construct header into a selection.
   ChangeLoopToSelection();
@@ -127,12 +127,8 @@
 
   // original_target_id must either be the merge target or continue construct
   // for the loop being operated on.
-  assert(original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kMergeNodeIndex) ||
-         original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kContinueNodeIndex));
+  assert(original_target_id == loop_construct_header_->MergeBlockId() ||
+         original_target_id == loop_construct_header_->ContinueBlockId());
 
   auto terminator = context_->cfg()->block(source_id)->terminator();
 
@@ -217,11 +213,11 @@
   // the "else" branch be the merge block.
   auto terminator = loop_construct_header_->terminator();
   if (terminator->opcode() == SpvOpBranch) {
-    analysis::Bool temp;
-    const analysis::Bool* bool_type =
+    opt::analysis::Bool temp;
+    const opt::analysis::Bool* bool_type =
         context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
     auto const_mgr = context_->get_constant_mgr();
-    auto true_const = const_mgr->GetConstant(bool_type, {true});
+    auto true_const = const_mgr->GetConstant(bool_type, {1});
     auto true_const_result_id =
         const_mgr->GetDefiningInstruction(true_const)->result_id();
     auto original_branch_id = terminator->GetSingleWordOperand(0);
@@ -250,6 +246,10 @@
       context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
                                                         Instruction* use,
                                                         uint32_t index) {
+        // Ignore uses outside of blocks, such as in OpDecorate.
+        if (context_->get_instr_block(use) == nullptr) {
+          return;
+        }
         // If a use is not appropriately dominated by its definition,
         // replace the use with an OpUndef, unless the definition is an
         // access chain, in which case replace it with some (possibly fresh)
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index 71b0f0e..f6c065b 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -23,8 +23,6 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
 // An opportunity to replace a structured loop with a selection.
 class StructuredLoopToSelectionReductionOpportunity
     : public ReductionOpportunity {
@@ -32,8 +30,8 @@
   // Constructs an opportunity from a loop header block and the function that
   // encloses it.
   explicit StructuredLoopToSelectionReductionOpportunity(
-      IRContext* context, BasicBlock* loop_construct_header,
-      Function* enclosing_function)
+      opt::IRContext* context, opt::BasicBlock* loop_construct_header,
+      opt::Function* enclosing_function)
       : context_(context),
         loop_construct_header_(loop_construct_header),
         enclosing_function_(enclosing_function) {}
@@ -67,11 +65,12 @@
   // Removes any components of |to_block|'s phi instructions relating to
   // |from_id|.
   void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
-                                          BasicBlock* to_block);
+                                          opt::BasicBlock* to_block);
 
   // Adds components to |to_block|'s phi instructions to account for a new
   // incoming edge from |from_id|.
-  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+                                        opt::BasicBlock* to_block);
 
   // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
   // following branch instruction accordingly.
@@ -87,9 +86,10 @@
   // 2) |def| is an OpVariable
   // 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
   // dominates b.
-  bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+  bool DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+                                          opt::Instruction* use,
                                           uint32_t use_index,
-                                          BasicBlock& def_block);
+                                          opt::BasicBlock& def_block);
 
   // Checks whether the global value list has an OpVariable of the given pointer
   // type, adding one if not, and returns the id of such an OpVariable.
@@ -105,9 +105,9 @@
   // be factored out in due course.
   uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
 
-  IRContext* context_;
-  BasicBlock* loop_construct_header_;
-  Function* enclosing_function_;
+  opt::IRContext* context_;
+  opt::BasicBlock* loop_construct_header_;
+  opt::Function* enclosing_function_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
similarity index 73%
rename from source/reduce/structured_loop_to_selection_reduction_pass.cpp
rename to source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
index 768a2e8..085b267 100644
--- a/source/reduce/structured_loop_to_selection_reduction_pass.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -12,13 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "structured_loop_to_selection_reduction_pass.h"
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 namespace {
 const uint32_t kMergeNodeIndex = 0;
@@ -26,17 +27,16 @@
 }  // namespace
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   std::set<uint32_t> merge_block_ids;
   for (auto& function : *context->module()) {
     for (auto& block : function) {
-      auto merge_inst = block.GetMergeInst();
-      if (merge_inst) {
-        merge_block_ids.insert(
-            merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+      auto merge_block_id = block.MergeBlockIdIfAny();
+      if (merge_block_id) {
+        merge_block_ids.insert(merge_block_id);
       }
     }
   }
@@ -50,11 +50,19 @@
         continue;
       }
 
+      uint32_t continue_block_id =
+          loop_merge_inst->GetSingleWordOperand(kContinueNodeIndex);
+
       // Check whether the loop construct's continue target is the merge block
       // of some structured control flow construct.  If it is, we cautiously do
       // not consider applying a transformation.
-      if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
-              kContinueNodeIndex)) != merge_block_ids.end()) {
+      if (merge_block_ids.find(continue_block_id) != merge_block_ids.end()) {
+        continue;
+      }
+
+      // Check whether the loop header block is also the continue target. If it
+      // is, we cautiously do not consider applying a transformation.
+      if (block.id() == continue_block_id) {
         continue;
       }
 
@@ -87,8 +95,9 @@
   return result;
 }
 
-std::string StructuredLoopToSelectionReductionPass::GetName() const {
-  return "StructuredLoopToSelectionReductionPass";
+std::string StructuredLoopToSelectionReductionOpportunityFinder::GetName()
+    const {
+  return "StructuredLoopToSelectionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..d63d434
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to turn structured loops into selections,
+// generalizing from a human-writable language the idea of turning a loop:
+//
+// while (c) {
+//   body;
+// }
+//
+// into:
+//
+// if (c) {
+//   body;
+// }
+//
+// Applying such opportunities results in continue constructs of transformed
+// loops becoming unreachable, so that it may be possible to remove them
+// subsequently.
+class StructuredLoopToSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  StructuredLoopToSelectionReductionOpportunityFinder() = default;
+
+  ~StructuredLoopToSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.h b/source/reduce/structured_loop_to_selection_reduction_pass.h
deleted file mode 100644
index a1f88bc..0000000
--- a/source/reduce/structured_loop_to_selection_reduction_pass.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// Turns structured loops into selections, generalizing from a human-writable
-// language the idea of turning a loop:
-//
-// while (c) {
-//   body;
-// }
-//
-// into:
-//
-// if (c) {
-//   body;
-// }
-//
-// The pass results in continue constructs of transformed loops becoming
-// unreachable; another pass for eliminating blocks may end up being able to
-// remove them.
-class StructuredLoopToSelectionReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit StructuredLoopToSelectionReductionPass(
-      const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~StructuredLoopToSelectionReductionPass() override = default;
-
-  std::string GetName() const final;
-
- protected:
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
diff --git a/source/spirv_fuzzer_options.cpp b/source/spirv_fuzzer_options.cpp
new file mode 100644
index 0000000..9a2cb9f
--- /dev/null
+++ b/source/spirv_fuzzer_options.cpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/spirv_fuzzer_options.h"
+
+namespace {
+// The default maximum number of steps for the reducer to run before giving up.
+const uint32_t kDefaultStepLimit = 250;
+}  // namespace
+
+spv_fuzzer_options_t::spv_fuzzer_options_t()
+    : has_random_seed(false),
+      random_seed(0),
+      shrinker_step_limit(kDefaultStepLimit) {}
+
+SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() {
+  return new spv_fuzzer_options_t();
+}
+
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsDestroy(spv_fuzzer_options options) {
+  delete options;
+}
+
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed(
+    spv_fuzzer_options options, uint32_t seed) {
+  options->has_random_seed = true;
+  options->random_seed = seed;
+}
+
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
+    spv_fuzzer_options options, uint32_t shrinker_step_limit) {
+  options->shrinker_step_limit = shrinker_step_limit;
+}
diff --git a/source/spirv_fuzzer_options.h b/source/spirv_fuzzer_options.h
new file mode 100644
index 0000000..3b8e15c
--- /dev/null
+++ b/source/spirv_fuzzer_options.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_SPIRV_FUZZER_OPTIONS_H_
+#define SOURCE_SPIRV_FUZZER_OPTIONS_H_
+
+#include "spirv-tools/libspirv.h"
+
+#include <string>
+#include <utility>
+
+// Manages command line options passed to the SPIR-V Fuzzer. New struct
+// members may be added for any new option.
+struct spv_fuzzer_options_t {
+  spv_fuzzer_options_t();
+
+  // See spvFuzzerOptionsSetRandomSeed.
+  bool has_random_seed;
+  uint32_t random_seed;
+
+  // See spvFuzzerOptionsSetShrinkerStepLimit.
+  uint32_t shrinker_step_limit;
+};
+
+#endif  // SOURCE_SPIRV_FUZZER_OPTIONS_H_
diff --git a/source/spirv_optimizer_options.cpp b/source/spirv_optimizer_options.cpp
index 30db4e2..e92ffc0 100644
--- a/source/spirv_optimizer_options.cpp
+++ b/source/spirv_optimizer_options.cpp
@@ -39,3 +39,13 @@
     spv_optimizer_options options, uint32_t val) {
   options->max_id_bound_ = val;
 }
+
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings(
+    spv_optimizer_options options, bool val) {
+  options->preserve_bindings_ = val;
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants(
+    spv_optimizer_options options, bool val) {
+  options->preserve_spec_constants_ = val;
+}
diff --git a/source/spirv_optimizer_options.h b/source/spirv_optimizer_options.h
index 1eb4d3f..aa76d20 100644
--- a/source/spirv_optimizer_options.h
+++ b/source/spirv_optimizer_options.h
@@ -24,7 +24,9 @@
   spv_optimizer_options_t()
       : run_validator_(true),
         val_options_(),
-        max_id_bound_(kDefaultMaxIdBound) {}
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {}
 
   // When true the validator will be run before optimizations are run.
   bool run_validator_;
@@ -36,5 +38,12 @@
   // this value must be at least 0x3FFFFF, but implementations can allow for a
   // higher value.
   uint32_t max_id_bound_;
+
+  // When true, all binding declarations within the module should be preserved.
+  bool preserve_bindings_;
+
+  // When true, all specialization constants within the module should be
+  // preserved.
+  bool preserve_spec_constants_;
 };
 #endif  // SOURCE_SPIRV_OPTIMIZER_OPTIONS_H_
diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp
index 110ea3e..5801d0a 100644
--- a/source/spirv_reducer_options.cpp
+++ b/source/spirv_reducer_options.cpp
@@ -17,6 +17,14 @@
 
 #include "source/spirv_reducer_options.h"
 
+namespace {
+// The default maximum number of steps the reducer will take before giving up.
+const uint32_t kDefaultStepLimit = 250;
+}  // namespace
+
+spv_reducer_options_t::spv_reducer_options_t()
+    : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
+
 SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() {
   return new spv_reducer_options_t();
 }
@@ -29,3 +37,8 @@
     spv_reducer_options options, uint32_t step_limit) {
   options->step_limit = step_limit;
 }
+
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+    spv_reducer_options options, bool fail_on_validation_error) {
+  options->fail_on_validation_error = fail_on_validation_error;
+}
diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h
index d48303c..1a431cc 100644
--- a/source/spirv_reducer_options.h
+++ b/source/spirv_reducer_options.h
@@ -20,16 +20,16 @@
 #include <string>
 #include <utility>
 
-// The default maximum number of steps for the reducer to run before giving up.
-const uint32_t kDefaultStepLimit = 250;
-
 // Manages command line options passed to the SPIR-V Reducer. New struct
 // members may be added for any new option.
 struct spv_reducer_options_t {
-  spv_reducer_options_t() : step_limit(kDefaultStepLimit) {}
+  spv_reducer_options_t();
 
-  // The number of steps the reducer will run for before giving up.
+  // See spvReducerOptionsSetStepLimit.
   uint32_t step_limit;
+
+  // See spvReducerOptionsSetFailOnValidationError.
+  bool fail_on_validation_error;
 };
 
 #endif  // SOURCE_SPIRV_REDUCER_OPTIONS_H_
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index a3aa0af..66856a0 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -15,6 +15,7 @@
 #include "source/spirv_target_env.h"
 
 #include <cstring>
+#include <string>
 
 #include "source/spirv_constant.h"
 #include "spirv-tools/libspirv.h"
@@ -61,6 +62,10 @@
       return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
     case SPV_ENV_WEBGPU_0:
       return "SPIR-V 1.3 (under WIP WebGPU semantics)";
+    case SPV_ENV_UNIVERSAL_1_4:
+      return "SPIR-V 1.4";
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return "SPIR-V 1.4 (under Vulkan 1.1 semantics)";
   }
   return "";
 }
@@ -91,78 +96,52 @@
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
       return SPV_SPIRV_VERSION_WORD(1, 3);
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return SPV_SPIRV_VERSION_WORD(1, 4);
   }
   return SPV_SPIRV_VERSION_WORD(0, 0);
 }
 
+static const std::pair<const char*, spv_target_env> spvTargetEnvNameMap[] = {
+    {"vulkan1.1spv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+    {"vulkan1.0", SPV_ENV_VULKAN_1_0},
+    {"vulkan1.1", SPV_ENV_VULKAN_1_1},
+    {"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},
+    {"opencl1.2embedded", SPV_ENV_OPENCL_EMBEDDED_1_2},
+    {"opencl1.2", SPV_ENV_OPENCL_1_2},
+    {"opencl2.0embedded", SPV_ENV_OPENCL_EMBEDDED_2_0},
+    {"opencl2.0", SPV_ENV_OPENCL_2_0},
+    {"opencl2.1embedded", SPV_ENV_OPENCL_EMBEDDED_2_1},
+    {"opencl2.1", SPV_ENV_OPENCL_2_1},
+    {"opencl2.2embedded", SPV_ENV_OPENCL_EMBEDDED_2_2},
+    {"opencl2.2", SPV_ENV_OPENCL_2_2},
+    {"opengl4.0", SPV_ENV_OPENGL_4_0},
+    {"opengl4.1", SPV_ENV_OPENGL_4_1},
+    {"opengl4.2", SPV_ENV_OPENGL_4_2},
+    {"opengl4.3", SPV_ENV_OPENGL_4_3},
+    {"opengl4.5", SPV_ENV_OPENGL_4_5},
+    {"webgpu0", SPV_ENV_WEBGPU_0},
+};
+
 bool spvParseTargetEnv(const char* s, spv_target_env* env) {
   auto match = [s](const char* b) {
     return s && (0 == strncmp(s, b, strlen(b)));
   };
-  if (match("vulkan1.0")) {
-    if (env) *env = SPV_ENV_VULKAN_1_0;
-    return true;
-  } else if (match("vulkan1.1")) {
-    if (env) *env = SPV_ENV_VULKAN_1_1;
-    return true;
-  } else if (match("spv1.0")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_0;
-    return true;
-  } else if (match("spv1.1")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_1;
-    return true;
-  } else if (match("spv1.2")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_2;
-    return true;
-  } else if (match("spv1.3")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_3;
-    return true;
-  } else if (match("opencl1.2embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_1_2;
-    return true;
-  } else if (match("opencl1.2")) {
-    if (env) *env = SPV_ENV_OPENCL_1_2;
-    return true;
-  } else if (match("opencl2.0embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_0;
-    return true;
-  } else if (match("opencl2.0")) {
-    if (env) *env = SPV_ENV_OPENCL_2_0;
-    return true;
-  } else if (match("opencl2.1embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_1;
-    return true;
-  } else if (match("opencl2.1")) {
-    if (env) *env = SPV_ENV_OPENCL_2_1;
-    return true;
-  } else if (match("opencl2.2embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_2;
-    return true;
-  } else if (match("opencl2.2")) {
-    if (env) *env = SPV_ENV_OPENCL_2_2;
-    return true;
-  } else if (match("opengl4.0")) {
-    if (env) *env = SPV_ENV_OPENGL_4_0;
-    return true;
-  } else if (match("opengl4.1")) {
-    if (env) *env = SPV_ENV_OPENGL_4_1;
-    return true;
-  } else if (match("opengl4.2")) {
-    if (env) *env = SPV_ENV_OPENGL_4_2;
-    return true;
-  } else if (match("opengl4.3")) {
-    if (env) *env = SPV_ENV_OPENGL_4_3;
-    return true;
-  } else if (match("opengl4.5")) {
-    if (env) *env = SPV_ENV_OPENGL_4_5;
-    return true;
-  } else if (match("webgpu0")) {
-    if (env) *env = SPV_ENV_WEBGPU_0;
-    return true;
-  } else {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_0;
-    return false;
+  for (auto& name_env : spvTargetEnvNameMap) {
+    if (match(name_env.first)) {
+      if (env) {
+        *env = name_env.second;
+      }
+      return true;
+    }
   }
+  if (env) *env = SPV_ENV_UNIVERSAL_1_0;
+  return false;
 }
 
 bool spvIsVulkanEnv(spv_target_env env) {
@@ -185,9 +164,11 @@
     case SPV_ENV_OPENCL_EMBEDDED_2_2:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       return false;
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return true;
   }
   return false;
@@ -207,6 +188,8 @@
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return false;
     case SPV_ENV_OPENCL_1_2:
     case SPV_ENV_OPENCL_EMBEDDED_1_2:
@@ -242,9 +225,109 @@
     case SPV_ENV_OPENCL_EMBEDDED_2_2:
     case SPV_ENV_OPENCL_2_1:
     case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return false;
     case SPV_ENV_WEBGPU_0:
       return true;
   }
   return false;
 }
+
+bool spvIsOpenGLEnv(spv_target_env env) {
+  switch (env) {
+    case SPV_ENV_UNIVERSAL_1_0:
+    case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_UNIVERSAL_1_2:
+    case SPV_ENV_UNIVERSAL_1_3:
+    case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_OPENCL_1_2:
+    case SPV_ENV_OPENCL_EMBEDDED_1_2:
+    case SPV_ENV_OPENCL_2_0:
+    case SPV_ENV_OPENCL_EMBEDDED_2_0:
+    case SPV_ENV_OPENCL_EMBEDDED_2_1:
+    case SPV_ENV_OPENCL_EMBEDDED_2_2:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return false;
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5:
+      return true;
+  }
+  return false;
+}
+
+bool spvIsVulkanOrWebGPUEnv(spv_target_env env) {
+  return spvIsVulkanEnv(env) || spvIsWebGPUEnv(env);
+}
+
+std::string spvLogStringForEnv(spv_target_env env) {
+  switch (env) {
+    case SPV_ENV_OPENCL_1_2:
+    case SPV_ENV_OPENCL_2_0:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_OPENCL_EMBEDDED_1_2:
+    case SPV_ENV_OPENCL_EMBEDDED_2_0:
+    case SPV_ENV_OPENCL_EMBEDDED_2_1:
+    case SPV_ENV_OPENCL_EMBEDDED_2_2: {
+      return "OpenCL";
+    }
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5: {
+      return "OpenGL";
+    }
+    case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4: {
+      return "Vulkan";
+    }
+    case SPV_ENV_WEBGPU_0: {
+      return "WebGPU";
+    }
+    case SPV_ENV_UNIVERSAL_1_0:
+    case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_UNIVERSAL_1_2:
+    case SPV_ENV_UNIVERSAL_1_3:
+    case SPV_ENV_UNIVERSAL_1_4: {
+      return "Universal";
+    }
+  }
+  return "Unknown";
+}
+
+std::string spvTargetEnvList(const int pad, const int wrap) {
+  std::string ret;
+  size_t max_line_len = wrap - pad;  // The first line isn't padded
+  std::string line;
+  std::string sep = "";
+
+  for (auto& name_env : spvTargetEnvNameMap) {
+    std::string word = sep + name_env.first;
+    if (line.length() + word.length() > max_line_len) {
+      // Adding one word wouldn't fit, commit the line in progress and
+      // start a new one.
+      ret += line + "\n";
+      line.assign(pad, ' ');
+      // The first line is done. The max length now comprises the
+      // padding.
+      max_line_len = wrap;
+    }
+    line += word;
+    sep = "|";
+  }
+
+  ret += line;
+
+  return ret;
+}
diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h
index d1bd835..1bdedf9 100644
--- a/source/spirv_target_env.h
+++ b/source/spirv_target_env.h
@@ -15,11 +15,9 @@
 #ifndef SOURCE_SPIRV_TARGET_ENV_H_
 #define SOURCE_SPIRV_TARGET_ENV_H_
 
-#include "spirv-tools/libspirv.h"
+#include <string>
 
-// Parses s into *env and returns true if successful.  If unparsable, returns
-// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
-bool spvParseTargetEnv(const char* s, spv_target_env* env);
+#include "spirv-tools/libspirv.h"
 
 // Returns true if |env| is a VULKAN environment, false otherwise.
 bool spvIsVulkanEnv(spv_target_env env);
@@ -30,7 +28,25 @@
 // Returns true if |env| is an WEBGPU environment, false otherwise.
 bool spvIsWebGPUEnv(spv_target_env env);
 
+// Returns true if |env| is an OPENGL environment, false otherwise.
+bool spvIsOpenGLEnv(spv_target_env env);
+
+// Returns true if |env| is a VULKAN or WEBGPU environment, false otherwise.
+bool spvIsVulkanOrWebGPUEnv(spv_target_env env);
+
 // Returns the version number for the given SPIR-V target environment.
 uint32_t spvVersionForTargetEnv(spv_target_env env);
 
+// Returns a string to use in logging messages that indicates the class of
+// environment, i.e. "Vulkan", "WebGPU", "OpenCL", etc.
+std::string spvLogStringForEnv(spv_target_env env);
+
+// 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
+//       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.
+std::string spvTargetEnvList(const int pad, const int wrap);
+
 #endif  // SOURCE_SPIRV_TARGET_ENV_H_
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index 2e9cf26..01aa797 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/spirv_validator_options.h"
+
 #include <cassert>
 #include <cstring>
 
-#include "source/spirv_validator_options.h"
-
 bool spvParseUniversalLimitsOptions(const char* s, spv_validator_limit* type) {
   auto match = [s](const char* b) {
     return s && (0 == strncmp(s, b, strlen(b)));
@@ -90,11 +90,22 @@
   options->relax_logical_pointer = val;
 }
 
+void spvValidatorOptionsSetBeforeHlslLegalization(spv_validator_options options,
+                                                  bool val) {
+  options->before_hlsl_legalization = val;
+  options->relax_logical_pointer = val;
+}
+
 void spvValidatorOptionsSetRelaxBlockLayout(spv_validator_options options,
                                             bool val) {
   options->relax_block_layout = val;
 }
 
+void spvValidatorOptionsSetUniformBufferStandardLayout(
+    spv_validator_options options, bool val) {
+  options->uniform_buffer_standard_layout = val;
+}
+
 void spvValidatorOptionsSetScalarBlockLayout(spv_validator_options options,
                                              bool val) {
   options->scalar_block_layout = val;
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index f426ebf..b7da5d8 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -43,15 +43,19 @@
         relax_struct_store(false),
         relax_logical_pointer(false),
         relax_block_layout(false),
+        uniform_buffer_standard_layout(false),
         scalar_block_layout(false),
-        skip_block_layout(false) {}
+        skip_block_layout(false),
+        before_hlsl_legalization(false) {}
 
   validator_universal_limits_t universal_limits_;
   bool relax_struct_store;
   bool relax_logical_pointer;
   bool relax_block_layout;
+  bool uniform_buffer_standard_layout;
   bool scalar_block_layout;
   bool skip_block_layout;
+  bool before_hlsl_legalization;
 };
 
 #endif  // SOURCE_SPIRV_VALIDATOR_OPTIONS_H_
diff --git a/source/table.cpp b/source/table.cpp
index b10d776..7890305 100644
--- a/source/table.cpp
+++ b/source/table.cpp
@@ -37,7 +37,9 @@
     case SPV_ENV_UNIVERSAL_1_2:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       break;
     default:
       return nullptr;
diff --git a/source/table.h b/source/table.h
index 64d73db..5adf04a 100644
--- a/source/table.h
+++ b/source/table.h
@@ -15,9 +15,8 @@
 #ifndef SOURCE_TABLE_H_
 #define SOURCE_TABLE_H_
 
-#include "source/latest_version_spirv_header.h"
-
 #include "source/extensions.h"
+#include "source/latest_version_spirv_header.h"
 #include "spirv-tools/libspirv.hpp"
 
 typedef struct spv_opcode_desc_t {
@@ -42,6 +41,7 @@
   // extensions. ~0u means reserved for future use. ~0u and non-empty extension
   // lists means only available in extensions.
   const uint32_t minVersion;
+  const uint32_t lastVersion;
 } spv_opcode_desc_t;
 
 typedef struct spv_operand_desc_t {
@@ -60,6 +60,7 @@
   // extensions. ~0u means reserved for future use. ~0u and non-empty extension
   // lists means only available in extensions.
   const uint32_t minVersion;
+  const uint32_t lastVersion;
 } spv_operand_desc_t;
 
 typedef struct spv_operand_desc_group_t {
diff --git a/source/text.cpp b/source/text.cpp
index adaf796..9eadf73 100644
--- a/source/text.cpp
+++ b/source/text.cpp
@@ -778,7 +778,7 @@
                              const size_t input_text_size, spv_binary* pBinary,
                              spv_diagnostic* pDiagnostic) {
   return spvTextToBinaryWithOptions(context, input_text, input_text_size,
-                                    SPV_BINARY_TO_TEXT_OPTION_NONE, pBinary,
+                                    SPV_TEXT_TO_BINARY_OPTION_NONE, pBinary,
                                     pDiagnostic);
 }
 
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 7b0cb2d..1564449 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -19,6 +19,7 @@
 #include <unordered_set>
 
 #include "source/val/function.h"
+#include "source/val/validation_state.h"
 
 namespace spvtools {
 namespace val {
@@ -73,7 +74,6 @@
   auto header = entry_block();
   auto merge = exit_block();
   assert(header);
-  assert(merge);
   int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header));
   ConstructBlockSet construct_blocks;
   std::unordered_set<BasicBlock*> corresponding_headers;
@@ -106,7 +106,8 @@
     // 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_depth == header_depth && type() == ConstructType::kSelection &&
+    if (block != header && block_depth == header_depth &&
+        type() == ConstructType::kSelection &&
         block->is_type(kBlockTypeContinue)) {
       // Continued to outer construct.
       continue;
@@ -127,5 +128,87 @@
   return construct_blocks;
 }
 
+bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const {
+  // Structured Exits:
+  // - Selection:
+  //  - branch to its merge
+  //  - branch to nearest enclosing loop merge or continue
+  //  - branch to nearest enclosing switch selection merge
+  // - Loop:
+  //  - branch to its merge
+  //  - branch to its continue
+  // - Continue:
+  //  - branch to loop header
+  //  - branch to loop merge
+  //
+  // Note: we will never see a case construct here.
+  assert(type() != ConstructType::kCase);
+  if (type() == ConstructType::kLoop) {
+    auto header = entry_block();
+    auto terminator = header->terminator();
+    auto index = terminator - &_.ordered_instructions()[0];
+    auto merge_inst = &_.ordered_instructions()[index - 1];
+    auto merge_block_id = merge_inst->GetOperandAs<uint32_t>(0u);
+    auto continue_block_id = merge_inst->GetOperandAs<uint32_t>(1u);
+    if (dest->id() == merge_block_id || dest->id() == continue_block_id) {
+      return true;
+    }
+  } else if (type() == ConstructType::kContinue) {
+    auto loop_construct = corresponding_constructs()[0];
+    auto header = loop_construct->entry_block();
+    auto terminator = header->terminator();
+    auto index = terminator - &_.ordered_instructions()[0];
+    auto merge_inst = &_.ordered_instructions()[index - 1];
+    auto merge_block_id = merge_inst->GetOperandAs<uint32_t>(0u);
+    if (dest == header || dest->id() == merge_block_id) {
+      return true;
+    }
+  } else {
+    assert(type() == ConstructType::kSelection);
+    if (dest == exit_block()) {
+      return true;
+    }
+
+    bool seen_switch = false;
+    auto header = entry_block();
+    auto block = header->immediate_dominator();
+    while (block) {
+      auto terminator = block->terminator();
+      auto index = terminator - &_.ordered_instructions()[0];
+      auto merge_inst = &_.ordered_instructions()[index - 1];
+      if (merge_inst->opcode() == SpvOpLoopMerge ||
+          (header->terminator()->opcode() != SpvOpSwitch &&
+           merge_inst->opcode() == SpvOpSelectionMerge &&
+           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)) {
+          block = block->immediate_dominator();
+          continue;
+        }
+
+        if ((!seen_switch || merge_inst->opcode() == SpvOpLoopMerge) &&
+            dest->id() == merge_target) {
+          return true;
+        } else if (merge_inst->opcode() == SpvOpLoopMerge) {
+          auto continue_target = merge_inst->GetOperandAs<uint32_t>(1u);
+          if (dest->id() == continue_target) {
+            return true;
+          }
+        }
+
+        if (terminator->opcode() == SpvOpSwitch) seen_switch = true;
+
+        // Hit an enclosing loop and didn't break or continue.
+        if (merge_inst->opcode() == SpvOpLoopMerge) return false;
+      }
+
+      block = block->immediate_dominator();
+    }
+  }
+
+  return false;
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/construct.h b/source/val/construct.h
index c7e7a78..9476760 100644
--- a/source/val/construct.h
+++ b/source/val/construct.h
@@ -23,6 +23,7 @@
 
 namespace spvtools {
 namespace val {
+class ValidationState_t;
 
 /// Functor for ordering BasicBlocks. BasicBlock pointers must not be null.
 struct less_than_id {
@@ -109,6 +110,24 @@
   // calculated.
   ConstructBlockSet blocks(Function* function) const;
 
+  // Returns true if |dest| is structured exit from the construct. Structured
+  // exits depend on the construct type.
+  // Selection:
+  //  * branch to the associated merge
+  //  * branch to the merge or continue of the innermost loop containing the
+  //  selection
+  //  * branch to the merge block of the innermost switch containing the
+  //  selection
+  //  Loop:
+  //  * branch to the associated merge or continue
+  // Continue:
+  //  * back-edge to the associated loop header
+  //  * branch to the associated loop merge
+  //
+  // Note: the validator does not generate case constructs. Switches are
+  // checked separately from other constructs.
+  bool IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const;
+
  private:
   /// The type of the construct
   ConstructType type_;
diff --git a/source/val/function.cpp b/source/val/function.cpp
index f638fb5..876a608 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -86,6 +86,12 @@
   continue_construct.set_corresponding_constructs({&loop_construct});
   loop_construct.set_corresponding_constructs({&continue_construct});
   merge_block_header_[&merge_block] = current_block_;
+  if (continue_target_headers_.find(&continue_target_block) ==
+      continue_target_headers_.end()) {
+    continue_target_headers_[&continue_target_block] = {current_block_};
+  } else {
+    continue_target_headers_[&continue_target_block].push_back(current_block_);
+  }
 
   return SPV_SUCCESS;
 }
@@ -311,13 +317,10 @@
   if (!bb_dom || bb == bb_dom) {
     // This block has no dominator, so it's at depth 0.
     block_depth_[bb] = 0;
-  } else if (bb->is_type(kBlockTypeMerge)) {
-    // If this is a merge block, its depth is equal to the block before
-    // branching.
-    BasicBlock* header = merge_block_header_[bb];
-    assert(header);
-    block_depth_[bb] = GetBlockDepth(header);
   } else if (bb->is_type(kBlockTypeContinue)) {
+    // This rule must precede the rule for merge blocks in order to set up
+    // depths correctly. If a block is both a merge and continue then the merge
+    // is nested within the continue's loop (or the graph is incorrect).
     // The depth of the continue block entry point is 1 + loop header depth.
     Construct* continue_construct =
         entry_block_to_construct_[std::make_pair(bb, ConstructType::kContinue)];
@@ -335,6 +338,12 @@
     } else {
       block_depth_[bb] = 1 + GetBlockDepth(loop_header);
     }
+  } else if (bb->is_type(kBlockTypeMerge)) {
+    // If this is a merge block, its depth is equal to the block before
+    // branching.
+    BasicBlock* header = merge_block_header_[bb];
+    assert(header);
+    block_depth_[bb] = GetBlockDepth(header);
   } else if (bb_dom->is_type(kBlockTypeHeader) ||
              bb_dom->is_type(kBlockTypeLoop)) {
     // The dominator of the given block is a header block. So, the nesting
@@ -383,5 +392,29 @@
   return return_value;
 }
 
+bool Function::CheckLimitations(const ValidationState_t& _,
+                                const Function* entry_point,
+                                std::string* reason) const {
+  bool return_value = true;
+  std::stringstream ss_reason;
+
+  for (const auto& is_compatible : limitations_) {
+    std::string message;
+    if (!is_compatible(_, entry_point, &message)) {
+      if (!reason) return false;
+      return_value = false;
+      if (!message.empty()) {
+        ss_reason << message << "\n";
+      }
+    }
+  }
+
+  if (!return_value && reason) {
+    *reason = ss_reason.str();
+  }
+
+  return return_value;
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/function.h b/source/val/function.h
index a052bbd..0d6873d 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -216,6 +216,16 @@
     execution_model_limitations_.push_back(is_compatible);
   }
 
+  /// Registers limitation with an |is_compatible| functor.
+  void RegisterLimitation(std::function<bool(const ValidationState_t& _,
+                                             const Function*, std::string*)>
+                              is_compatible) {
+    limitations_.push_back(is_compatible);
+  }
+
+  bool CheckLimitations(const ValidationState_t& _, const Function* entry_point,
+                        std::string* reason) const;
+
   /// Returns true if the given execution model passes the limitations stored in
   /// execution_model_limitations_. Returns false otherwise and fills optional
   /// |reason| parameter.
@@ -232,6 +242,28 @@
     return function_call_targets_;
   }
 
+  // Returns the block containing the OpSelectionMerge or OpLoopMerge that
+  // references |merge_block|.
+  // Values of |merge_block_header_| inserted by CFGPass, so do not call before
+  // the first iteration of ordered instructions in
+  // ValidateBinaryUsingContextAndValidationState has completed.
+  BasicBlock* GetMergeHeader(BasicBlock* merge_block) {
+    return merge_block_header_[merge_block];
+  }
+
+  // Returns vector of the blocks containing a OpLoopMerge that references
+  // |continue_target|.
+  // Values of |continue_target_headers_| inserted by CFGPass, so do not call
+  // before the first iteration of ordered instructions in
+  // ValidateBinaryUsingContextAndValidationState has completed.
+  std::vector<BasicBlock*> GetContinueHeaders(BasicBlock* continue_target) {
+    if (continue_target_headers_.find(continue_target) ==
+        continue_target_headers_.end()) {
+      return {};
+    }
+    return continue_target_headers_[continue_target];
+  }
+
  private:
   // Computes the representation of the augmented CFG.
   // Populates augmented_successors_map_ and augmented_predecessors_map_.
@@ -340,6 +372,10 @@
   /// This map provides the header block for a given merge block.
   std::unordered_map<BasicBlock*, BasicBlock*> merge_block_header_;
 
+  /// This map provides the header blocks for a given continue target.
+  std::unordered_map<BasicBlock*, std::vector<BasicBlock*>>
+      continue_target_headers_;
+
   /// Stores the control flow nesting depth of a given basic block
   std::unordered_map<BasicBlock*, int> block_depth_;
 
@@ -350,6 +386,12 @@
   std::list<std::function<bool(SpvExecutionModel, std::string*)>>
       execution_model_limitations_;
 
+  /// Stores limitations imposed by instructions used within the function.
+  /// Similar to execution_model_limitations_;
+  std::list<std::function<bool(const ValidationState_t& _, const Function*,
+                               std::string*)>>
+      limitations_;
+
   /// Stores ids of all functions called from this function.
   std::set<uint32_t> function_call_targets_;
 };
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 9797d31..6f1a26c 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -14,10 +14,9 @@
 
 #include "source/val/validate.h"
 
+#include <algorithm>
 #include <cassert>
 #include <cstdio>
-
-#include <algorithm>
 #include <functional>
 #include <iterator>
 #include <memory>
@@ -52,21 +51,6 @@
 namespace val {
 namespace {
 
-// TODO(umar): Validate header
-// TODO(umar): The binary parser validates the magic word, and the length of the
-// header, but nothing else.
-spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
-                       uint32_t version, uint32_t generator, uint32_t id_bound,
-                       uint32_t) {
-  // Record the ID bound so that the validator can ensure no ID is out of bound.
-  ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
-  _.setIdBound(id_bound);
-  _.setGenerator(generator);
-  _.setVersion(version);
-
-  return SPV_SUCCESS;
-}
-
 // Parses OpExtension instruction and registers extension.
 void RegisterExtension(ValidationState_t& _,
                        const spv_parsed_instruction_t* inst) {
@@ -110,48 +94,6 @@
   return SPV_SUCCESS;
 }
 
-void printDot(const ValidationState_t& _, const BasicBlock& other) {
-  std::string block_string;
-  if (other.successors()->empty()) {
-    block_string += "end ";
-  } else {
-    for (auto block : *other.successors()) {
-      block_string += _.getIdName(block->id()) + " ";
-    }
-  }
-  printf("%10s -> {%s\b}\n", _.getIdName(other.id()).c_str(),
-         block_string.c_str());
-}
-
-void PrintBlocks(ValidationState_t& _, Function func) {
-  assert(func.first_block());
-
-  printf("%10s -> %s\n", _.getIdName(func.id()).c_str(),
-         _.getIdName(func.first_block()->id()).c_str());
-  for (const auto& block : func.ordered_blocks()) {
-    printDot(_, *block);
-  }
-}
-
-#ifdef __clang__
-#define UNUSED(func) [[gnu::unused]] func
-#elif defined(__GNUC__)
-#define UNUSED(func)            \
-  func __attribute__((unused)); \
-  func
-#elif defined(_MSC_VER)
-#define UNUSED(func) func
-#endif
-
-UNUSED(void PrintDotGraph(ValidationState_t& _, Function func)) {
-  if (func.first_block()) {
-    std::string func_name(_.getIdName(func.id()));
-    printf("digraph %s {\n", func_name.c_str());
-    PrintBlocks(_, func);
-    printf("}\n");
-  }
-}
-
 spv_result_t ValidateForwardDecls(ValidationState_t& _) {
   if (_.unresolved_forward_id_count() == 0) return SPV_SUCCESS;
 
@@ -249,8 +191,7 @@
 
     // For Vulkan and WebGPU, the static function-call graph for an entry point
     // must not contain cycles.
-    if (spvIsWebGPUEnv(_.context()->target_env) ||
-        spvIsVulkanEnv(_.context()->target_env)) {
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
       if (_.recursive_entry_points().find(entry_point) !=
           _.recursive_entry_points().end()) {
         return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
@@ -282,6 +223,12 @@
            << "Invalid SPIR-V magic number.";
   }
 
+  if (spvIsWebGPUEnv(context.target_env) && endian != SPV_ENDIANNESS_LITTLE) {
+    return DiagnosticStream(position, context.consumer, "",
+                            SPV_ERROR_INVALID_BINARY)
+           << "WebGPU requires SPIR-V to be little endian.";
+  }
+
   spv_header_t header;
   if (spvBinaryHeaderGet(binary.get(), endian, &header)) {
     return DiagnosticStream(position, context.consumer, "",
@@ -319,7 +266,8 @@
 
   // Parse the module and perform inline validation checks. These checks do
   // not require the the knowledge of the whole module.
-  if (auto error = spvBinaryParse(&context, vstate, words, num_words, setHeader,
+  if (auto error = spvBinaryParse(&context, vstate, words, num_words,
+                                  /*parsed_header =*/nullptr,
                                   ProcessInstruction, pDiagnostic)) {
     return error;
   }
@@ -386,7 +334,6 @@
       Instruction* inst = const_cast<Instruction*>(&instruction);
       vstate->RegisterInstruction(inst);
     }
-    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
   }
 
   if (!vstate->has_memory_model_specified())
@@ -400,13 +347,26 @@
   // Catch undefined forward references before performing further checks.
   if (auto error = ValidateForwardDecls(*vstate)) return error;
 
+  // ID usage needs be handled in its own iteration of the instructions,
+  // between the two others. It depends on the first loop to have been
+  // finished, so that all instructions have been registered. And the following
+  // loop depends on all of the usage data being populated. Thus it cannot live
+  // in either of those iterations.
+  // It should also live after the forward declaration check, since it will
+  // have problems with missing forward declarations, but give less useful error
+  // messages.
+  for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
+    auto& instruction = vstate->ordered_instructions()[i];
+    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
+  }
+
   // Validate individual opcodes.
   for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
     auto& instruction = vstate->ordered_instructions()[i];
 
     // Keep these passes in the order they appear in the SPIR-V specification
     // sections to maintain test consistency.
-    // Miscellaneous
+    if (auto error = MiscPass(*vstate, &instruction)) return error;
     if (auto error = DebugPass(*vstate, &instruction)) return error;
     if (auto error = AnnotationPass(*vstate, &instruction)) return error;
     if (auto error = ExtensionPass(*vstate, &instruction)) return error;
@@ -452,6 +412,7 @@
   // those checks register the limitation checked here.
   for (const auto inst : vstate->ordered_instructions()) {
     if (auto error = ValidateExecutionLimitations(*vstate, &inst)) return error;
+    if (auto error = ValidateSmallTypeUses(*vstate, &inst)) return error;
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validate.h b/source/val/validate.h
index fe357a2..b6c4072 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -199,12 +199,22 @@
 /// Validates correctness of function instructions.
 spv_result_t FunctionPass(ValidationState_t& _, const Instruction* inst);
 
+/// Validates correctness of miscellaneous instructions.
+spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst);
+
 /// Validates execution limitations.
 ///
 /// Verifies execution models are allowed for all functionality they contain.
 spv_result_t ValidateExecutionLimitations(ValidationState_t& _,
                                           const Instruction* inst);
 
+/// Validates restricted  uses of 8- and 16-bit types.
+///
+/// Validates shaders that uses 8- or 16-bit storage capabilities, but not full
+/// capabilities only have appropriate uses of those types.
+spv_result_t ValidateSmallTypeUses(ValidationState_t& _,
+                                   const Instruction* inst);
+
 /// @brief Validate the ID's within a SPIR-V binary
 ///
 /// @param[in] pInstructions array of instructions
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 621ace2..2695280 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -12,17 +12,193 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include "source/opcode.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+bool IsValidWebGPUDecoration(uint32_t decoration) {
+  switch (decoration) {
+    case SpvDecorationSpecId:
+    case SpvDecorationBlock:
+    case SpvDecorationRowMajor:
+    case SpvDecorationColMajor:
+    case SpvDecorationArrayStride:
+    case SpvDecorationMatrixStride:
+    case SpvDecorationBuiltIn:
+    case SpvDecorationNoPerspective:
+    case SpvDecorationFlat:
+    case SpvDecorationCentroid:
+    case SpvDecorationRestrict:
+    case SpvDecorationAliased:
+    case SpvDecorationNonWritable:
+    case SpvDecorationNonReadable:
+    case SpvDecorationUniform:
+    case SpvDecorationLocation:
+    case SpvDecorationComponent:
+    case SpvDecorationIndex:
+    case SpvDecorationBinding:
+    case SpvDecorationDescriptorSet:
+    case SpvDecorationOffset:
+    case SpvDecorationNoContraction:
+      return true;
+    default:
+      return false;
+  }
+}
+
+std::string LogStringForDecoration(uint32_t decoration) {
+  switch (decoration) {
+    case SpvDecorationRelaxedPrecision:
+      return "RelaxedPrecision";
+    case SpvDecorationSpecId:
+      return "SpecId";
+    case SpvDecorationBlock:
+      return "Block";
+    case SpvDecorationBufferBlock:
+      return "BufferBlock";
+    case SpvDecorationRowMajor:
+      return "RowMajor";
+    case SpvDecorationColMajor:
+      return "ColMajor";
+    case SpvDecorationArrayStride:
+      return "ArrayStride";
+    case SpvDecorationMatrixStride:
+      return "MatrixStride";
+    case SpvDecorationGLSLShared:
+      return "GLSLShared";
+    case SpvDecorationGLSLPacked:
+      return "GLSLPacked";
+    case SpvDecorationCPacked:
+      return "CPacked";
+    case SpvDecorationBuiltIn:
+      return "BuiltIn";
+    case SpvDecorationNoPerspective:
+      return "NoPerspective";
+    case SpvDecorationFlat:
+      return "Flat";
+    case SpvDecorationPatch:
+      return "Patch";
+    case SpvDecorationCentroid:
+      return "Centroid";
+    case SpvDecorationSample:
+      return "Sample";
+    case SpvDecorationInvariant:
+      return "Invariant";
+    case SpvDecorationRestrict:
+      return "Restrict";
+    case SpvDecorationAliased:
+      return "Aliased";
+    case SpvDecorationVolatile:
+      return "Volatile";
+    case SpvDecorationConstant:
+      return "Constant";
+    case SpvDecorationCoherent:
+      return "Coherent";
+    case SpvDecorationNonWritable:
+      return "NonWritable";
+    case SpvDecorationNonReadable:
+      return "NonReadable";
+    case SpvDecorationUniform:
+      return "Uniform";
+    case SpvDecorationSaturatedConversion:
+      return "SaturatedConversion";
+    case SpvDecorationStream:
+      return "Stream";
+    case SpvDecorationLocation:
+      return "Location";
+    case SpvDecorationComponent:
+      return "Component";
+    case SpvDecorationIndex:
+      return "Index";
+    case SpvDecorationBinding:
+      return "Binding";
+    case SpvDecorationDescriptorSet:
+      return "DescriptorSet";
+    case SpvDecorationOffset:
+      return "Offset";
+    case SpvDecorationXfbBuffer:
+      return "XfbBuffer";
+    case SpvDecorationXfbStride:
+      return "XfbStride";
+    case SpvDecorationFuncParamAttr:
+      return "FuncParamAttr";
+    case SpvDecorationFPRoundingMode:
+      return "FPRoundingMode";
+    case SpvDecorationFPFastMathMode:
+      return "FPFastMathMode";
+    case SpvDecorationLinkageAttributes:
+      return "LinkageAttributes";
+    case SpvDecorationNoContraction:
+      return "NoContraction";
+    case SpvDecorationInputAttachmentIndex:
+      return "InputAttachmentIndex";
+    case SpvDecorationAlignment:
+      return "Alignment";
+    case SpvDecorationMaxByteOffset:
+      return "MaxByteOffset";
+    case SpvDecorationAlignmentId:
+      return "AlignmentId";
+    case SpvDecorationMaxByteOffsetId:
+      return "MaxByteOffsetId";
+    case SpvDecorationNoSignedWrap:
+      return "NoSignedWrap";
+    case SpvDecorationNoUnsignedWrap:
+      return "NoUnsignedWrap";
+    case SpvDecorationExplicitInterpAMD:
+      return "ExplicitInterpAMD";
+    case SpvDecorationOverrideCoverageNV:
+      return "OverrideCoverageNV";
+    case SpvDecorationPassthroughNV:
+      return "PassthroughNV";
+    case SpvDecorationViewportRelativeNV:
+      return "ViewportRelativeNV";
+    case SpvDecorationSecondaryViewportRelativeNV:
+      return "SecondaryViewportRelativeNV";
+    case SpvDecorationPerPrimitiveNV:
+      return "PerPrimitiveNV";
+    case SpvDecorationPerViewNV:
+      return "PerViewNV";
+    case SpvDecorationPerTaskNV:
+      return "PerTaskNV";
+    case SpvDecorationPerVertexNV:
+      return "PerVertexNV";
+    case SpvDecorationNonUniformEXT:
+      return "NonUniformEXT";
+    case SpvDecorationRestrictPointerEXT:
+      return "RestrictPointerEXT";
+    case SpvDecorationAliasedPointerEXT:
+      return "AliasedPointerEXT";
+    case SpvDecorationHlslCounterBufferGOOGLE:
+      return "HlslCounterBufferGOOGLE";
+    case SpvDecorationHlslSemanticGOOGLE:
+      return "HlslSemanticGOOGLE";
+    default:
+      break;
+  }
+  return "Unknown";
+}
+
+// Returns true if the decoration takes ID parameters.
+// TODO(dneto): This can be generated from the grammar.
+bool DecorationTakesIdParameters(uint32_t type) {
+  switch (static_cast<SpvDecoration>(type)) {
+    case SpvDecorationUniformId:
+    case SpvDecorationAlignmentId:
+    case SpvDecorationMaxByteOffsetId:
+    case SpvDecorationHlslCounterBufferGOOGLE:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
 spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
   const auto decoration = inst->GetOperandAs<uint32_t>(1);
   if (decoration == SpvDecorationSpecId) {
@@ -31,14 +207,39 @@
     if (!target || !spvOpcodeIsScalarSpecConstant(target->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpDecorate SpecId decoration target <id> '"
-             << _.getIdName(decoration)
+             << _.getIdName(target_id)
              << "' is not a scalar specialization constant.";
     }
   }
+
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsValidWebGPUDecoration(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpDecorate decoration '" << LogStringForDecoration(decoration)
+           << "' is not valid for the WebGPU execution environment.";
+  }
+
+  if (DecorationTakesIdParameters(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Decorations taking ID parameters may not be used with "
+              "OpDecorateId";
+  }
   // TODO: Add validations for all decorations.
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateDecorateId(ValidationState_t& _, const Instruction* inst) {
+  const auto decoration = inst->GetOperandAs<uint32_t>(1);
+  if (!DecorationTakesIdParameters(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Decorations that don't take ID parameters may not be used with "
+              "OpDecorateId";
+  }
+  // TODO: Add validations for these decorations.
+  // UniformId is covered elsewhere.
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateMemberDecorate(ValidationState_t& _,
                                     const Instruction* inst) {
   const auto struct_type_id = inst->GetOperandAs<uint32_t>(0);
@@ -59,6 +260,15 @@
            << " is out of bounds. The structure has " << member_count
            << " members. Largest valid index is " << member_count - 1 << ".";
   }
+
+  const auto decoration = inst->GetOperandAs<uint32_t>(2);
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsValidWebGPUDecoration(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpMemberDecorate decoration  '" << _.getIdName(decoration)
+           << "' is not valid for the WebGPU execution environment.";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -76,11 +286,11 @@
     auto use = pair.first;
     if (use->opcode() != SpvOpDecorate && use->opcode() != SpvOpGroupDecorate &&
         use->opcode() != SpvOpGroupMemberDecorate &&
-        use->opcode() != SpvOpName) {
+        use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Result id of OpDecorationGroup can only "
              << "be targeted by OpName, OpGroupDecorate, "
-             << "OpDecorate, and OpGroupMemberDecorate";
+             << "OpDecorate, OpDecorateId, and OpGroupMemberDecorate";
     }
   }
   return SPV_SUCCESS;
@@ -161,7 +371,8 @@
 spv_result_t RegisterDecorations(ValidationState_t& _,
                                  const Instruction* inst) {
   switch (inst->opcode()) {
-    case SpvOpDecorate: {
+    case SpvOpDecorate:
+    case SpvOpDecorateId: {
       const uint32_t target_id = inst->word(1);
       const SpvDecoration dec_type = static_cast<SpvDecoration>(inst->word(2));
       std::vector<uint32_t> dec_params;
@@ -236,6 +447,11 @@
     case SpvOpDecorate:
       if (auto error = ValidateDecorate(_, inst)) return error;
       break;
+    case SpvOpDecorateId:
+      if (auto error = ValidateDecorateId(_, inst)) return error;
+      break;
+    // TODO(dneto): SpvOpDecorateStringGOOGLE
+    // See https://github.com/KhronosGroup/SPIRV-Tools/issues/2253
     case SpvOpMemberDecorate:
       if (auto error = ValidateMemberDecorate(_, inst)) return error;
       break;
diff --git a/source/val/validate_arithmetics.cpp b/source/val/validate_arithmetics.cpp
index 2314e7d..433330d 100644
--- a/source/val/validate_arithmetics.cpp
+++ b/source/val/validate_arithmetics.cpp
@@ -39,8 +39,11 @@
     case SpvOpFRem:
     case SpvOpFMod:
     case SpvOpFNegate: {
+      bool supportsCoopMat =
+          (opcode != SpvOpFMul && opcode != SpvOpFRem && opcode != SpvOpFMod);
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !(supportsCoopMat && _.IsFloatCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected floating scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -58,8 +61,11 @@
 
     case SpvOpUDiv:
     case SpvOpUMod: {
+      bool supportsCoopMat = (opcode == SpvOpUDiv);
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !(supportsCoopMat &&
+            _.IsUnsignedIntCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -82,7 +88,10 @@
     case SpvOpSMod:
     case SpvOpSRem:
     case SpvOpSNegate: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      bool supportsCoopMat =
+          (opcode != SpvOpIMul && opcode != SpvOpSRem && opcode != SpvOpSMod);
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !(supportsCoopMat && _.IsIntCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -94,7 +103,8 @@
            ++operand_index) {
         const uint32_t type_id = _.GetOperandTypeId(inst, operand_index);
         if (!type_id ||
-            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id)))
+            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id) &&
+             !(supportsCoopMat && _.IsIntCooperativeMatrixType(result_type))))
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Expected int scalar or vector type as operand: "
                  << spvOpcodeString(opcode) << " operand index "
@@ -176,7 +186,8 @@
     }
 
     case SpvOpMatrixTimesScalar: {
-      if (!_.IsFloatMatrixType(result_type))
+      if (!_.IsFloatMatrixType(result_type) &&
+          !_.IsCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float matrix type as Result Type: "
                << spvOpcodeString(opcode);
@@ -442,6 +453,92 @@
       break;
     }
 
+    case SpvOpCooperativeMatrixMulAddNV: {
+      const uint32_t D_type_id = _.GetOperandTypeId(inst, 1);
+      const uint32_t A_type_id = _.GetOperandTypeId(inst, 2);
+      const uint32_t B_type_id = _.GetOperandTypeId(inst, 3);
+      const uint32_t C_type_id = _.GetOperandTypeId(inst, 4);
+
+      if (!_.IsCooperativeMatrixType(A_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as A Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(B_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as B Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(C_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as C Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(D_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as Result Type: "
+               << spvOpcodeString(opcode);
+      }
+
+      const auto A = _.FindDef(A_type_id);
+      const auto B = _.FindDef(B_type_id);
+      const auto C = _.FindDef(C_type_id);
+      const auto D = _.FindDef(D_type_id);
+
+      std::tuple<bool, bool, uint32_t> A_scope, B_scope, C_scope, D_scope,
+          A_rows, B_rows, C_rows, D_rows, A_cols, B_cols, C_cols, D_cols;
+
+      A_scope = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(2));
+      B_scope = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(2));
+      C_scope = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(2));
+      D_scope = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(2));
+
+      A_rows = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(3));
+      B_rows = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(3));
+      C_rows = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(3));
+      D_rows = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(3));
+
+      A_cols = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(4));
+      B_cols = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(4));
+      C_cols = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(4));
+      D_cols = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(4));
+
+      const auto notEqual = [](std::tuple<bool, bool, uint32_t> X,
+                               std::tuple<bool, bool, uint32_t> Y) {
+        return (std::get<1>(X) && std::get<1>(Y) &&
+                std::get<2>(X) != std::get<2>(Y));
+      };
+
+      if (notEqual(A_scope, B_scope) || notEqual(A_scope, C_scope) ||
+          notEqual(A_scope, D_scope) || notEqual(B_scope, C_scope) ||
+          notEqual(B_scope, D_scope) || notEqual(C_scope, D_scope)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix scopes must match: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(A_rows, C_rows) || notEqual(A_rows, D_rows) ||
+          notEqual(C_rows, D_rows)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'M' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(B_cols, C_cols) || notEqual(B_cols, D_cols) ||
+          notEqual(C_cols, D_cols)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'N' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(A_cols, B_rows)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'K' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+      break;
+    }
+
     default:
       break;
   }
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index 38c7053..b8867dd 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -25,6 +25,28 @@
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
+namespace {
+
+bool IsStorageClassAllowedByUniversalRules(uint32_t storage_class) {
+  switch (storage_class) {
+    case SpvStorageClassUniform:
+    case SpvStorageClassStorageBuffer:
+    case SpvStorageClassWorkgroup:
+    case SpvStorageClassCrossWorkgroup:
+    case SpvStorageClassGeneric:
+    case SpvStorageClassAtomicCounter:
+    case SpvStorageClassImage:
+    case SpvStorageClassFunction:
+    case SpvStorageClassPhysicalStorageBufferEXT:
+      return true;
+      break;
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
 namespace spvtools {
 namespace val {
 
@@ -119,32 +141,42 @@
                << ": expected Pointer to be of type OpTypePointer";
       }
 
-      switch (storage_class) {
-        case SpvStorageClassUniform:
-        case SpvStorageClassWorkgroup:
-        case SpvStorageClassCrossWorkgroup:
-        case SpvStorageClassGeneric:
-        case SpvStorageClassAtomicCounter:
-        case SpvStorageClassImage:
-        case SpvStorageClassStorageBuffer:
-        case SpvStorageClassPhysicalStorageBufferEXT:
-          break;
-        default:
-          if (spvIsOpenCLEnv(_.context()->target_env)) {
-            if (storage_class != SpvStorageClassFunction) {
-              return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                     << spvOpcodeString(opcode)
-                     << ": expected Pointer Storage Class to be Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, AtomicCounter, "
-                        "Image, StorageBuffer or Function";
-            }
-          } else {
+      // Validate storage class against universal rules
+      if (!IsStorageClassAllowedByUniversalRules(storage_class)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(opcode)
+               << ": storage class forbidden by universal validation rules.";
+      }
+
+      // Then Shader rules
+      if (_.HasCapability(SpvCapabilityShader)) {
+        if (storage_class == SpvStorageClassFunction) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << spvOpcodeString(opcode)
+                 << ": Function storage class forbidden when the Shader "
+                    "capability is declared.";
+        }
+      }
+
+      // And finally OpenCL environment rules
+      if (spvIsOpenCLEnv(_.context()->target_env)) {
+        if ((storage_class != SpvStorageClassFunction) &&
+            (storage_class != SpvStorageClassWorkgroup) &&
+            (storage_class != SpvStorageClassCrossWorkgroup) &&
+            (storage_class != SpvStorageClassGeneric)) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << spvOpcodeString(opcode)
+                 << ": storage class must be Function, Workgroup, "
+                    "CrossWorkGroup or Generic in the OpenCL environment.";
+        }
+
+        if (_.context()->target_env == SPV_ENV_OPENCL_1_2) {
+          if (storage_class == SpvStorageClassGeneric) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << spvOpcodeString(opcode)
-                   << ": expected Pointer Storage Class to be Uniform, "
-                      "Workgroup, CrossWorkgroup, Generic, AtomicCounter, "
-                      "Image or StorageBuffer";
+                   << "Storage class cannot be Generic in OpenCL 1.2 "
+                      "environment";
           }
+        }
       }
 
       if (opcode == SpvOpAtomicFlagTestAndSet ||
@@ -175,13 +207,37 @@
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, operand_index++))
+      const auto equal_semantics_index = operand_index++;
+      if (auto error = ValidateMemorySemantics(_, inst, equal_semantics_index))
         return error;
 
       if (opcode == SpvOpAtomicCompareExchange ||
           opcode == SpvOpAtomicCompareExchangeWeak) {
-        if (auto error = ValidateMemorySemantics(_, inst, operand_index++))
+        const auto unequal_semantics_index = operand_index++;
+        if (auto error =
+                ValidateMemorySemantics(_, inst, unequal_semantics_index))
           return error;
+
+        // Volatile bits must match for equal and unequal semantics. Previous
+        // checks guarantee they are 32-bit constants, but we need to recheck
+        // whether they are evaluatable constants.
+        bool is_int32 = false;
+        bool is_equal_const = false;
+        bool is_unequal_const = false;
+        uint32_t equal_value = 0;
+        uint32_t unequal_value = 0;
+        std::tie(is_int32, is_equal_const, equal_value) = _.EvalInt32IfConst(
+            inst->GetOperandAs<uint32_t>(equal_semantics_index));
+        std::tie(is_int32, is_unequal_const, unequal_value) =
+            _.EvalInt32IfConst(
+                inst->GetOperandAs<uint32_t>(unequal_semantics_index));
+        if (is_equal_const && is_unequal_const &&
+            ((equal_value & SpvMemorySemanticsVolatileMask) ^
+             (unequal_value & SpvMemorySemanticsVolatileMask))) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "Volatile mask setting must match for Equal and Unequal "
+                    "memory semantics";
+        }
       }
 
       if (opcode == SpvOpAtomicStore) {
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index 4fbe9c9..b499c8c 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -14,8 +14,6 @@
 
 // Validates correctness of barrier SPIR-V instructions.
 
-#include "source/val/validate.h"
-
 #include <string>
 
 #include "source/diagnostic.h"
@@ -24,6 +22,7 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validate_memory_semantics.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
@@ -38,8 +37,7 @@
 
   switch (opcode) {
     case SpvOpControlBarrier: {
-      if (spvVersionForTargetEnv(_.context()->target_env) <
-          SPV_SPIRV_VERSION_WORD(1, 3)) {
+      if (_.version() < SPV_SPIRV_VERSION_WORD(1, 3)) {
         _.function(inst->function()->id())
             ->RegisterExecutionModelLimitation(
                 [](SpvExecutionModel model, std::string* message) {
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index aaba324..a9bf716 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -60,12 +60,22 @@
                                const Instruction& inst,
                                uint32_t* underlying_type) {
   if (decoration.struct_member_index() != Decoration::kInvalidMember) {
-    assert(inst.opcode() == SpvOpTypeStruct);
+    if (inst.opcode() != SpvOpTypeStruct) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << GetIdDesc(inst)
+             << "Attempted to get underlying data type via member index for "
+                "non-struct type.";
+    }
     *underlying_type = inst.word(decoration.struct_member_index() + 2);
     return SPV_SUCCESS;
   }
 
-  assert(inst.opcode() != SpvOpTypeStruct);
+  if (inst.opcode() == SpvOpTypeStruct) {
+    return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+           << GetIdDesc(inst)
+           << " did not find an member index to get underlying data type for "
+              "struct type.";
+  }
 
   if (spvOpcodeIsConstant(inst.opcode())) {
     *underlying_type = inst.type_id();
@@ -101,6 +111,28 @@
   return SpvStorageClassMax;
 }
 
+bool IsBuiltInValidForWebGPU(SpvBuiltIn label) {
+  switch (label) {
+    case SpvBuiltInPosition:
+    case SpvBuiltInVertexIndex:
+    case SpvBuiltInInstanceIndex:
+    case SpvBuiltInFrontFacing:
+    case SpvBuiltInFragCoord:
+    case SpvBuiltInFragDepth:
+    case SpvBuiltInNumWorkgroups:
+    case SpvBuiltInWorkgroupSize:
+    case SpvBuiltInLocalInvocationId:
+    case SpvBuiltInGlobalInvocationId:
+    case SpvBuiltInLocalInvocationIndex: {
+      return true;
+    }
+    default:
+      break;
+  }
+
+  return false;
+}
+
 // Helper class managing validation of built-ins.
 // TODO: Generic functionality of this class can be moved into
 // ValidationState_t to be made available to other users.
@@ -169,11 +201,26 @@
                                                const Instruction& inst);
   spv_result_t ValidateVertexIdOrInstanceIdAtDefinition(
       const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateLocalInvocationIndexAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
   spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration,
                                                  const Instruction& inst);
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
       const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateSMBuiltinsAtDefinition(const Decoration& decoration,
+                                              const Instruction& inst);
+
+  // Used for SubgroupEqMask, SubgroupGeMask, SubgroupGtMask, SubgroupLtMask,
+  // SubgroupLeMask.
+  spv_result_t ValidateI32Vec4InputAtDefinition(const Decoration& decoration,
+                                                const Instruction& inst);
+  // Used for SubgroupLocalInvocationId, SubgroupSize.
+  spv_result_t ValidateI32InputAtDefinition(const Decoration& decoration,
+                                            const Instruction& inst);
+  // Used for SubgroupId, NumSubgroups.
+  spv_result_t ValidateComputeI32InputAtDefinition(const Decoration& decoration,
+                                                   const Instruction& inst);
 
   // The following section contains functions which are called when id defined
   // by |referenced_inst| is
@@ -267,6 +314,11 @@
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidateLocalInvocationIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   spv_result_t ValidateVertexIndexAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
@@ -292,6 +344,16 @@
       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(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
 
   // Validates that |built_in_inst| is not (even indirectly) referenced from
   // within a function which can be called with |execution_model|.
@@ -324,6 +386,13 @@
   spv_result_t ValidateI32Arr(
       const Decoration& decoration, const Instruction& inst,
       const std::function<spv_result_t(const std::string& message)>& diag);
+  spv_result_t ValidateOptionalArrayedI32(
+      const Decoration& decoration, const Instruction& inst,
+      const std::function<spv_result_t(const std::string& message)>& diag);
+  spv_result_t ValidateI32Helper(
+      const Decoration& decoration, const Instruction& inst,
+      const std::function<spv_result_t(const std::string& message)>& diag,
+      uint32_t underlying_type);
   spv_result_t ValidateF32(
       const Decoration& decoration, const Instruction& inst,
       const std::function<spv_result_t(const std::string& message)>& diag);
@@ -504,6 +573,30 @@
     return error;
   }
 
+  return ValidateI32Helper(decoration, inst, diag, underlying_type);
+}
+
+spv_result_t BuiltInsValidator::ValidateOptionalArrayedI32(
+    const Decoration& decoration, const Instruction& inst,
+    const std::function<spv_result_t(const std::string& message)>& diag) {
+  uint32_t underlying_type = 0;
+  if (spv_result_t error =
+          GetUnderlyingType(_, decoration, inst, &underlying_type)) {
+    return error;
+  }
+
+  // Strip the array, if present.
+  if (_.GetIdOpcode(underlying_type) == SpvOpTypeArray) {
+    underlying_type = _.FindDef(underlying_type)->word(2u);
+  }
+
+  return ValidateI32Helper(decoration, inst, diag, underlying_type);
+}
+
+spv_result_t BuiltInsValidator::ValidateI32Helper(
+    const Decoration& decoration, const Instruction& inst,
+    const std::function<spv_result_t(const std::string& message)>& diag,
+    uint32_t underlying_type) {
   if (!_.IsIntScalarType(underlying_type)) {
     return diag(GetDefinitionDesc(decoration, inst) + " is not an int scalar.");
   }
@@ -937,12 +1030,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFragCoordAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateF32Vec(
             decoration, inst, 4,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FragCoord "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FragCoord "
                         "variable needs to be a 4-component 32-bit float "
                         "vector. "
                      << message;
@@ -959,12 +1054,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FragCoord to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FragCoord to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -974,7 +1070,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FragCoord to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FragCoord to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -994,12 +1091,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFragDepthAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateF32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FragDepth "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FragDepth "
                         "variable needs to be a 32-bit float scalar. "
                      << message;
             })) {
@@ -1015,12 +1114,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FragDepth to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FragDepth to be only used for "
                 "variables with Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1030,7 +1130,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FragDepth to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FragDepth to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1043,7 +1144,8 @@
       const auto* modes = _.GetExecutionModes(entry_point);
       if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec requires DepthReplacing execution mode to be "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec requires DepthReplacing execution mode to be "
                   "declared when using BuiltIn FragDepth. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst);
@@ -1063,12 +1165,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFrontFacingAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateBool(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FrontFacing "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FrontFacing "
                         "variable needs to be a bool scalar. "
                      << message;
             })) {
@@ -1084,12 +1188,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FrontFacing to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FrontFacing to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1099,7 +1204,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FrontFacing to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FrontFacing to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1233,12 +1339,14 @@
 
 spv_result_t BuiltInsValidator::ValidateInstanceIndexAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn InstanceIndex "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn InstanceIndex "
                         "variable needs to be a 32-bit int scalar. "
                      << message;
             })) {
@@ -1254,12 +1362,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn InstanceIndex to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn InstanceIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1269,7 +1378,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn InstanceIndex to be used only "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn InstanceIndex to be used only "
                   "with Vertex execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1617,6 +1727,46 @@
     }
   }
 
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassOutput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << "WebGPU spec allows BuiltIn Position to be only used for "
+                "variables with Output storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      switch (execution_model) {
+        case SpvExecutionModelVertex: {
+          if (spv_result_t error = ValidateF32Vec(
+                  decoration, built_in_inst, 4,
+                  [this, &referenced_from_inst](
+                      const std::string& message) -> spv_result_t {
+                    return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                           << "According to the WebGPU spec BuiltIn Position "
+                              "variable needs to be a 4-component 32-bit float "
+                              "vector. "
+                           << message;
+                  })) {
+            return error;
+          }
+          break;
+        }
+        default: {
+          return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << "WebGPU spec allows BuiltIn Position to be used only "
+                    "with the Vertex execution model. "
+                 << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                     referenced_from_inst, execution_model);
+        }
+      }
+    }
+  }
+
   if (function_id_ == 0) {
     // Propagate this rule to all dependant ids in the global scope.
     id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
@@ -1630,15 +1780,31 @@
 spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (spv_result_t error = ValidateI32(
-            decoration, inst,
-            [this, &inst](const std::string& message) -> spv_result_t {
-              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn PrimitiveId "
-                        "variable needs to be a 32-bit int scalar. "
-                     << message;
-            })) {
-      return error;
+    // PrimitiveId can be a per-primitive variable for mesh shader stage.
+    // In such cases variable will have an array of 32-bit integers.
+    if (decoration.struct_member_index() != Decoration::kInvalidMember) {
+      // This must be a 32-bit int scalar.
+      if (spv_result_t error = ValidateI32(
+              decoration, inst,
+              [this, &inst](const std::string& message) -> spv_result_t {
+                return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << "According to the Vulkan spec BuiltIn PrimitiveId "
+                          "variable needs to be a 32-bit int scalar. "
+                       << message;
+              })) {
+        return error;
+      }
+    } else {
+      if (spv_result_t error = ValidateOptionalArrayedI32(
+              decoration, inst,
+              [this, &inst](const std::string& message) -> spv_result_t {
+                return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << "According to the Vulkan spec BuiltIn PrimitiveId "
+                          "variable needs to be a 32-bit int scalar. "
+                       << message;
+              })) {
+        return error;
+      }
     }
   }
 
@@ -2075,13 +2241,15 @@
 
 spv_result_t BuiltInsValidator::ValidateVertexIndexAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn VertexIndex "
-                        "variable needs to be a 32-bit int scalar. "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn VertexIndex variable needs to be a "
+                        "32-bit int scalar. "
                      << message;
             })) {
       return error;
@@ -2143,16 +2311,75 @@
   return SPV_SUCCESS;
 }
 
-spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference(
+spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the WebGPU spec BuiltIn "
+                        "LocalInvocationIndex variable needs to be a 32-bit "
+                        "int."
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateLocalInvocationIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtReference(
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn VertexIndex to be only used for "
+             << "WebGPU spec allows BuiltIn LocalInvocationIndex to be only "
+                "used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelGLCompute) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << "WebGPU spec allows BuiltIn VertexIndex to be used only "
+                  "with GLCompute execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
+        std::bind(&BuiltInsValidator::ValidateLocalInvocationIndexAtReference,
+                  this, decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn VertexIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -2162,8 +2389,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn VertexIndex to be used only "
-                  "with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn VertexIndex to be used only with "
                   "Vertex execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -2184,17 +2411,37 @@
 spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (spv_result_t error = ValidateI32(
-            decoration, inst,
-            [this, &decoration,
-             &inst](const std::string& message) -> spv_result_t {
-              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn "
-                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                      decoration.params()[0])
-                     << "variable needs to be a 32-bit int scalar. " << message;
-            })) {
-      return error;
+    // This can be a per-primitive variable for mesh shader stage.
+    // In such cases variable will have an array of 32-bit integers.
+    if (decoration.struct_member_index() != Decoration::kInvalidMember) {
+      // This must be a 32-bit int scalar.
+      if (spv_result_t error = ValidateI32(
+              decoration, inst,
+              [this, &decoration,
+               &inst](const std::string& message) -> spv_result_t {
+                return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << "According to the Vulkan spec BuiltIn "
+                       << _.grammar().lookupOperandName(
+                              SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0])
+                       << "variable needs to be a 32-bit int scalar. "
+                       << message;
+              })) {
+        return error;
+      }
+    } else {
+      if (spv_result_t error = ValidateOptionalArrayedI32(
+              decoration, inst,
+              [this, &decoration,
+               &inst](const std::string& message) -> spv_result_t {
+                return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << "According to the Vulkan spec BuiltIn "
+                       << _.grammar().lookupOperandName(
+                              SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0])
+                       << "variable needs to be a 32-bit int scalar. "
+                       << message;
+              })) {
+        return error;
+      }
     }
   }
 
@@ -2299,13 +2546,15 @@
 
 spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32Vec(
             decoration, inst, 3,
             [this, &decoration,
              &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
                      << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                       decoration.params()[0])
                      << " variable needs to be a 3-component 32-bit int "
@@ -2325,12 +2574,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                               decoration.params()[0])
              << " to be only used for variables with Input storage class. "
@@ -2340,11 +2590,15 @@
     }
 
     for (const SpvExecutionModel execution_model : execution_models_) {
-      if (execution_model != SpvExecutionModelGLCompute &&
-          execution_model != SpvExecutionModelTaskNV &&
-          execution_model != SpvExecutionModelMeshNV) {
+      bool has_vulkan_model = execution_model == SpvExecutionModelGLCompute ||
+                              execution_model == SpvExecutionModelTaskNV ||
+                              execution_model == SpvExecutionModelMeshNV;
+      bool has_webgpu_model = execution_model == SpvExecutionModelGLCompute;
+      if ((spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) ||
+          (spvIsWebGPUEnv(_.context()->target_env) && !has_webgpu_model)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
                << " to be used only with GLCompute execution model. "
@@ -2365,10 +2619,176 @@
   return SPV_SUCCESS;
 }
 
-spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition(
+spv_result_t BuiltInsValidator::ValidateComputeI32InputAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (!spvOpcodeIsConstant(inst.opcode())) {
+    if (decoration.struct_member_index() != Decoration::kInvalidMember) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << "BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " cannot be used as a member decoration ";
+    }
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &decoration,
+             &inst](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int "
+                        "vector. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateComputeI32InputAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateComputeI32InputAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " 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_) {
+      bool has_vulkan_model = execution_model == SpvExecutionModelGLCompute ||
+                              execution_model == SpvExecutionModelTaskNV ||
+                              execution_model == SpvExecutionModelMeshNV;
+      if (spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                decoration.params()[0])
+               << " to be used only with GLCompute execution model. "
+               << 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::ValidateComputeI32InputAtReference, this,
+                  decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateI32InputAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (decoration.struct_member_index() != Decoration::kInvalidMember) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << "BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " cannot be used as a member decoration ";
+    }
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &decoration,
+             &inst](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int. " << message;
+            })) {
+      return error;
+    }
+
+    const SpvStorageClass storage_class = GetStorageClass(inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, inst, inst, inst) << " "
+             << GetStorageClassDesc(inst);
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateI32Vec4InputAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (decoration.struct_member_index() != Decoration::kInvalidMember) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << "BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " cannot be used as a member decoration ";
+    }
+    if (spv_result_t error = ValidateI32Vec(
+            decoration, inst, 4,
+            [this, &decoration,
+             &inst](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 4-component 32-bit int "
+                        "vector. "
+                     << message;
+            })) {
+      return error;
+    }
+
+    const SpvStorageClass storage_class = GetStorageClass(inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, inst, inst, inst) << " "
+             << GetStorageClassDesc(inst);
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    if (spvIsVulkanEnv(_.context()->target_env) &&
+        !spvOpcodeIsConstant(inst.opcode())) {
       return _.diag(SPV_ERROR_INVALID_DATA, &inst)
              << "Vulkan spec requires BuiltIn WorkgroupSize to be a "
                 "constant. "
@@ -2379,9 +2799,10 @@
             decoration, inst, 3,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn WorkgroupSize "
-                        "variable "
-                        "needs to be a 3-component 32-bit int vector. "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn WorkgroupSize variable needs to be a "
+                        "3-component 32-bit int vector. "
                      << message;
             })) {
       return error;
@@ -2396,11 +2817,12 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelGLCompute) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
                << " to be used only with GLCompute execution model. "
@@ -2420,9 +2842,73 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateSMBuiltinsAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for "
+                "variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+  }
+
+  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,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
+
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsBuiltInValidForWebGPU(label)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+           << "WebGPU does not allow BuiltIn "
+           << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                            decoration.params()[0]);
+  }
+
   // If you are adding a new BuiltIn enum, please register it here.
   // If the newly added enum has validation rules associated with it
   // consider leaving a TODO and/or creating an issue.
@@ -2483,6 +2969,21 @@
     case SpvBuiltInSamplePosition: {
       return ValidateSamplePositionAtDefinition(decoration, inst);
     }
+    case SpvBuiltInSubgroupId:
+    case SpvBuiltInNumSubgroups: {
+      return ValidateComputeI32InputAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInSubgroupLocalInvocationId:
+    case SpvBuiltInSubgroupSize: {
+      return ValidateI32InputAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInSubgroupEqMask:
+    case SpvBuiltInSubgroupGeMask:
+    case SpvBuiltInSubgroupGtMask:
+    case SpvBuiltInSubgroupLeMask:
+    case SpvBuiltInSubgroupLtMask: {
+      return ValidateI32Vec4InputAtDefinition(decoration, inst);
+    }
     case SpvBuiltInTessCoord: {
       return ValidateTessCoordAtDefinition(decoration, inst);
     }
@@ -2502,23 +3003,22 @@
     case SpvBuiltInInstanceId: {
       return ValidateVertexIdOrInstanceIdAtDefinition(decoration, inst);
     }
-    case SpvBuiltInLocalInvocationIndex:
+    case SpvBuiltInLocalInvocationIndex: {
+      return ValidateLocalInvocationIndexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInWarpsPerSMNV:
+    case SpvBuiltInSMCountNV:
+    case SpvBuiltInWarpIDNV:
+    case SpvBuiltInSMIDNV: {
+      return ValidateSMBuiltinsAtDefinition(decoration, inst);
+    }
     case SpvBuiltInWorkDim:
     case SpvBuiltInGlobalSize:
     case SpvBuiltInEnqueuedWorkgroupSize:
     case SpvBuiltInGlobalOffset:
     case SpvBuiltInGlobalLinearId:
-    case SpvBuiltInSubgroupSize:
     case SpvBuiltInSubgroupMaxSize:
-    case SpvBuiltInNumSubgroups:
     case SpvBuiltInNumEnqueuedSubgroups:
-    case SpvBuiltInSubgroupId:
-    case SpvBuiltInSubgroupLocalInvocationId:
-    case SpvBuiltInSubgroupEqMaskKHR:
-    case SpvBuiltInSubgroupGeMaskKHR:
-    case SpvBuiltInSubgroupGtMaskKHR:
-    case SpvBuiltInSubgroupLeMaskKHR:
-    case SpvBuiltInSubgroupLtMaskKHR:
     case SpvBuiltInBaseVertex:
     case SpvBuiltInBaseInstance:
     case SpvBuiltInDrawIndex:
@@ -2656,14 +3156,15 @@
 
 // Validates correctness of built-in variables.
 spv_result_t ValidateBuiltIns(ValidationState_t& _) {
-  if (!spvIsVulkanEnv(_.context()->target_env)) {
-    // Early return. All currently implemented rules are based on Vulkan spec.
+  if (!spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    // Early return. All currently implemented rules are based on Vulkan or
+    // WebGPU spec.
     //
     // TODO: If you are adding validation rules for environments other than
-    // Vulkan (or general rules which are not environment independent), then you
-    // need to modify or remove this condition. Consider also adding early
-    // returns into BuiltIn-specific rules, so that the system doesn't spawn new
-    // rules which don't do anything.
+    // Vulkan or WebGPU (or general rules which are not environment
+    // independent), then you need to modify or remove this condition. Consider
+    // also adding early returns into BuiltIn-specific rules, so that the system
+    // doesn't spawn new rules which don't do anything.
     return SPV_SUCCESS;
   }
 
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 8fe30a8..8d8839e 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -29,6 +29,7 @@
 
 #include "source/cfa.h"
 #include "source/opcode.h"
+#include "source/spirv_target_env.h"
 #include "source/spirv_validator_options.h"
 #include "source/val/basic_block.h"
 #include "source/val/construct.h"
@@ -112,6 +113,19 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateBranch(ValidationState_t& _, const Instruction* inst) {
+  // target operands must be OpLabel
+  const auto id = inst->GetOperandAs<uint32_t>(0);
+  const auto target = _.FindDef(id);
+  if (!target || SpvOpLabel != target->opcode()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "'Target Label' operands for OpBranch must be the ID "
+              "of an OpLabel instruction";
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateBranchConditional(ValidationState_t& _,
                                        const Instruction* inst) {
   // num_operands is either 3 or 5 --- if 5, the last two need to be literal
@@ -155,6 +169,26 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateSwitch(ValidationState_t& _, const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  // At least two operands (selector, default), any more than that are
+  // literal/target.
+
+  // target operands must be OpLabel
+  for (size_t i = 2; i < num_operands; i += 2) {
+    // literal, id
+    const auto id = inst->GetOperandAs<uint32_t>(i + 1);
+    const auto target = _.FindDef(id);
+    if (!target || SpvOpLabel != target->opcode()) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "'Target Label' operands for OpSwitch must be IDs of an "
+                "OpLabel instruction";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateReturnValue(ValidationState_t& _,
                                  const Instruction* inst) {
   const auto value_id = inst->GetOperandAs<uint32_t>(0);
@@ -196,6 +230,82 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateLoopMerge(ValidationState_t& _, const Instruction* inst) {
+  const auto merge_id = inst->GetOperandAs<uint32_t>(0);
+  const auto merge = _.FindDef(merge_id);
+  if (!merge || merge->opcode() != SpvOpLabel) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Merge Block " << _.getIdName(merge_id) << " must be an OpLabel";
+  }
+  if (merge_id == inst->block()->id()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Merge Block may not be the block containing the OpLoopMerge\n";
+  }
+
+  const auto continue_id = inst->GetOperandAs<uint32_t>(1);
+  const auto continue_target = _.FindDef(continue_id);
+  if (!continue_target || continue_target->opcode() != SpvOpLabel) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Continue Target " << _.getIdName(continue_id)
+           << " must be an OpLabel";
+  }
+
+  if (merge_id == continue_id) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Merge Block and Continue Target must be different ids";
+  }
+
+  const auto loop_control = inst->GetOperandAs<uint32_t>(2);
+  if ((loop_control >> SpvLoopControlUnrollShift) & 0x1 &&
+      (loop_control >> SpvLoopControlDontUnrollShift) & 0x1) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Unroll and DontUnroll loop controls must not both be specified";
+  }
+  if ((loop_control >> SpvLoopControlDontUnrollShift) & 0x1 &&
+      (loop_control >> SpvLoopControlPeelCountShift) & 0x1) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PeelCount and DontUnroll "
+                                                   "loop controls must not "
+                                                   "both be specified";
+  }
+  if ((loop_control >> SpvLoopControlDontUnrollShift) & 0x1 &&
+      (loop_control >> SpvLoopControlPartialCountShift) & 0x1) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PartialCount and "
+                                                   "DontUnroll loop controls "
+                                                   "must not both be specified";
+  }
+
+  uint32_t operand = 3;
+  if ((loop_control >> SpvLoopControlDependencyLengthShift) & 0x1) {
+    ++operand;
+  }
+  if ((loop_control >> SpvLoopControlMinIterationsShift) & 0x1) {
+    ++operand;
+  }
+  if ((loop_control >> SpvLoopControlMaxIterationsShift) & 0x1) {
+    ++operand;
+  }
+  if ((loop_control >> SpvLoopControlIterationMultipleShift) & 0x1) {
+    if (inst->operands().size() < operand ||
+        inst->GetOperandAs<uint32_t>(operand) == 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst) << "IterationMultiple loop "
+                                                     "control operand must be "
+                                                     "greater than zero";
+    }
+    ++operand;
+  }
+  if ((loop_control >> SpvLoopControlPeelCountShift) & 0x1) {
+    ++operand;
+  }
+  if ((loop_control >> SpvLoopControlPartialCountShift) & 0x1) {
+    ++operand;
+  }
+
+  // That the right number of operands is present is checked by the parser. The
+  // above code tracks operands for expanded validation checking in the future.
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 void printDominatorList(const BasicBlock& b) {
@@ -383,41 +493,54 @@
   std::map<uint32_t, uint32_t> num_fall_through_targeted;
   uint32_t default_case_fall_through = 0u;
   uint32_t default_target = switch_inst->GetOperandAs<uint32_t>(1u);
-  std::unordered_set<uint32_t> seen;
+  bool default_appears_multiple_times = false;
+  for (uint32_t i = 3; i < switch_inst->operands().size(); i += 2) {
+    if (default_target == switch_inst->GetOperandAs<uint32_t>(i)) {
+      default_appears_multiple_times = true;
+      break;
+    }
+  }
+  std::unordered_map<uint32_t, uint32_t> seen_to_fall_through;
   for (uint32_t i = 1; i < switch_inst->operands().size(); i += 2) {
     uint32_t target = switch_inst->GetOperandAs<uint32_t>(i);
     if (target == merge->id()) continue;
 
-    if (!seen.insert(target).second) continue;
-
-    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)) {
-      return _.diag(SPV_ERROR_INVALID_CFG, header->label())
-             << "Selection header " << _.getIdName(header->id())
-             << " does not dominate its case construct " << _.getIdName(target);
-    }
-
     uint32_t case_fall_through = 0u;
-    if (auto error = FindCaseFallThrough(_, target_block, &case_fall_through,
-                                         merge, case_targets, function)) {
-      return error;
-    }
-
-    // Track how many time the fall through case has been targeted.
-    if (case_fall_through != 0u) {
-      auto where = num_fall_through_targeted.lower_bound(case_fall_through);
-      if (where == num_fall_through_targeted.end() ||
-          where->first != case_fall_through) {
-        num_fall_through_targeted.insert(where,
-                                         std::make_pair(case_fall_through, 1));
-      } else {
-        where->second++;
+    auto seen_iter = seen_to_fall_through.find(target);
+    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)) {
+        return _.diag(SPV_ERROR_INVALID_CFG, header->label())
+               << "Selection header " << _.getIdName(header->id())
+               << " does not dominate its case construct "
+               << _.getIdName(target);
       }
+
+      if (auto error = FindCaseFallThrough(_, target_block, &case_fall_through,
+                                           merge, case_targets, function)) {
+        return error;
+      }
+
+      // Track how many time the fall through case has been targeted.
+      if (case_fall_through != 0u) {
+        auto where = num_fall_through_targeted.lower_bound(case_fall_through);
+        if (where == num_fall_through_targeted.end() ||
+            where->first != case_fall_through) {
+          num_fall_through_targeted.insert(
+              where, std::make_pair(case_fall_through, 1));
+        } else {
+          where->second++;
+        }
+      }
+      seen_to_fall_through.insert(std::make_pair(target, case_fall_through));
+    } else {
+      case_fall_through = seen_iter->second;
     }
 
-    if (case_fall_through == default_target) {
+    if (case_fall_through == default_target &&
+        !default_appears_multiple_times) {
       case_fall_through = default_case_fall_through;
     }
     if (case_fall_through != 0u) {
@@ -546,16 +669,27 @@
       }
     }
 
-    // Check that for all non-header blocks, all predecessors are within this
-    // construct.
     Construct::ConstructBlockSet construct_blocks = construct.blocks(function);
     for (auto block : construct_blocks) {
+      std::string construct_name, header_name, exit_name;
+      std::tie(construct_name, header_name, exit_name) =
+          ConstructNames(construct.type());
+      // Check that all exits from the construct are via structured exits.
+      for (auto succ : *block->successors()) {
+        if (block->reachable() && !construct_blocks.count(succ) &&
+            !construct.IsStructuredExit(_, succ)) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "block <ID> " << _.getIdName(block->id()) << " exits the "
+                 << construct_name << " headed by <ID> "
+                 << _.getIdName(header->id())
+                 << ", but not via a structured exit";
+        }
+      }
       if (block == header) continue;
+      // 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)) {
-          std::string construct_name, header_name, exit_name;
-          std::tie(construct_name, header_name, exit_name) =
-              ConstructNames(construct.type());
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id()))
                  << "block <ID> " << pred->id() << " branches to the "
                  << construct_name << " construct, but not to the "
@@ -574,6 +708,121 @@
       }
     }
   }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t PerformWebGPUCfgChecks(ValidationState_t& _, Function* function) {
+  for (auto& block : function->ordered_blocks()) {
+    if (block->reachable()) continue;
+    if (block->is_type(kBlockTypeMerge)) {
+      // 1. Find the referencing merge and confirm that it is reachable.
+      BasicBlock* merge_header = function->GetMergeHeader(block);
+      assert(merge_header != nullptr);
+      if (!merge_header->reachable()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must be referenced by "
+                  "a reachable merge instruction.";
+      }
+
+      // 2. Check that the only instructions are OpLabel and OpUnreachable.
+      auto* label_inst = block->label();
+      auto* terminator_inst = block->terminator();
+      assert(label_inst != nullptr);
+      assert(terminator_inst != nullptr);
+
+      if (terminator_inst->opcode() != SpvOpUnreachable) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must terminate with "
+                  "OpUnreachable.";
+      }
+
+      auto label_idx = label_inst - &_.ordered_instructions()[0];
+      auto terminator_idx = terminator_inst - &_.ordered_instructions()[0];
+      if (label_idx + 1 != terminator_idx) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must only contain an "
+                  "OpLabel and OpUnreachable instruction.";
+      }
+
+      // 3. Use label instruction to confirm there is no uses by branches.
+      for (auto use : label_inst->uses()) {
+        const auto* use_inst = use.first;
+        if (spvOpcodeIsBranch(use_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable merge-blocks cannot be the target "
+                    "of a branch.";
+        }
+      }
+    } else if (block->is_type(kBlockTypeContinue)) {
+      // 1. Find referencing loop and confirm that it is reachable.
+      std::vector<BasicBlock*> continue_headers =
+          function->GetContinueHeaders(block);
+      if (continue_headers.empty()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must be referenced "
+                  "by a loop instruction.";
+      }
+
+      std::vector<BasicBlock*> reachable_headers(continue_headers.size());
+      auto iter =
+          std::copy_if(continue_headers.begin(), continue_headers.end(),
+                       reachable_headers.begin(),
+                       [](BasicBlock* header) { return header->reachable(); });
+      reachable_headers.resize(std::distance(reachable_headers.begin(), iter));
+
+      if (reachable_headers.empty()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must be referenced "
+                  "by a reachable loop instruction.";
+      }
+
+      // 2. Check that the only instructions are OpLabel and OpBranch.
+      auto* label_inst = block->label();
+      auto* terminator_inst = block->terminator();
+      assert(label_inst != nullptr);
+      assert(terminator_inst != nullptr);
+
+      if (terminator_inst->opcode() != SpvOpBranch) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must terminate with "
+                  "OpBranch.";
+      }
+
+      auto label_idx = label_inst - &_.ordered_instructions()[0];
+      auto terminator_idx = terminator_inst - &_.ordered_instructions()[0];
+      if (label_idx + 1 != terminator_idx) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must only contain "
+                  "an OpLabel and an OpBranch instruction.";
+      }
+
+      // 3. Use label instruction to confirm there is no uses by branches.
+      for (auto use : label_inst->uses()) {
+        const auto* use_inst = use.first;
+        if (spvOpcodeIsBranch(use_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable continue-target cannot be the "
+                    "target of a branch.";
+        }
+      }
+
+      // 4. Confirm that continue-target has a back edge to a reachable loop
+      //    header block.
+      auto branch_target = terminator_inst->GetOperandAs<uint32_t>(0);
+      for (auto* continue_header : reachable_headers) {
+        if (branch_target != continue_header->id()) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable continue-target must only have a "
+                    "back edge to a single reachable loop instruction.";
+        }
+      }
+    } else {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+             << "For WebGPU, all blocks must be reachable, unless they are "
+             << "degenerate cases of merge-block or continue-target.";
+    }
+  }
   return SPV_SUCCESS;
 }
 
@@ -616,7 +865,8 @@
       auto edges = CFA<BasicBlock>::CalculateDominators(
           postorder, function.AugmentedCFGPredecessorsFunction());
       for (auto edge : edges) {
-        edge.first->SetImmediateDominator(edge.second);
+        if (edge.first != edge.second)
+          edge.first->SetImmediateDominator(edge.second);
       }
 
       /// calculate post dominators
@@ -656,6 +906,13 @@
                    << _.getIdName(idom->id());
           }
         }
+
+        // For WebGPU check that all unreachable blocks are degenerate cases for
+        // merge-block or continue-target.
+        if (spvIsWebGPUEnv(_.context()->target_env)) {
+          spv_result_t result = PerformWebGPUCfgChecks(_, &function);
+          if (result != SPV_SUCCESS) return result;
+        }
       }
       // If we have structed control flow, check that no block has a control
       // flow nesting depth larger than the limit.
@@ -764,12 +1021,21 @@
     case SpvOpPhi:
       if (auto error = ValidatePhi(_, inst)) return error;
       break;
+    case SpvOpBranch:
+      if (auto error = ValidateBranch(_, inst)) return error;
+      break;
     case SpvOpBranchConditional:
       if (auto error = ValidateBranchConditional(_, inst)) return error;
       break;
     case SpvOpReturnValue:
       if (auto error = ValidateReturnValue(_, inst)) return error;
       break;
+    case SpvOpSwitch:
+      if (auto error = ValidateSwitch(_, inst)) return error;
+      break;
+    case SpvOpLoopMerge:
+      if (auto error = ValidateLoopMerge(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validate_composites.cpp b/source/val/validate_composites.cpp
index ccc5587..9b7f592 100644
--- a/source/val/validate_composites.cpp
+++ b/source/val/validate_composites.cpp
@@ -118,6 +118,10 @@
         *member_type = type_inst->word(component_index + 2);
         break;
       }
+      case SpvOpTypeCooperativeMatrixNV: {
+        *member_type = type_inst->word(2);
+        break;
+      }
       default:
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Reached non-composite type while indexes still remain to "
@@ -154,6 +158,12 @@
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Expected Index to be int scalar";
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot extract from a vector of 8- or 16-bit types";
+  }
   return SPV_SUCCESS;
 }
 
@@ -184,6 +194,12 @@
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Expected Index to be int scalar";
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot insert into a vector of 8- or 16-bit types";
+  }
   return SPV_SUCCESS;
 }
 
@@ -315,11 +331,37 @@
 
       break;
     }
+    case SpvOpTypeCooperativeMatrixNV: {
+      const auto result_type_inst = _.FindDef(result_type);
+      assert(result_type_inst);
+      const auto component_type_id =
+          result_type_inst->GetOperandAs<uint32_t>(1);
+
+      if (3 != num_operands) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected single constituent";
+      }
+
+      const uint32_t operand_type_id = _.GetOperandTypeId(inst, 2);
+
+      if (operand_type_id != component_type_id) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Constituent type to be equal to the component type";
+      }
+
+      break;
+    }
     default: {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Expected Result Type to be a composite type";
     }
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot create a composite containing 8- or 16-bit types";
+  }
   return SPV_SUCCESS;
 }
 
@@ -338,6 +380,12 @@
               "the composite (Op"
            << spvOpcodeString(_.GetIdOpcode(member_type)) << ").";
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot extract from a composite of 8- or 16-bit types";
+  }
   return SPV_SUCCESS;
 }
 
@@ -367,6 +415,12 @@
               "Composite (Op"
            << spvOpcodeString(_.GetIdOpcode(member_type)) << ").";
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot insert into a composite of 8- or 16-bit types";
+  }
   return SPV_SUCCESS;
 }
 
@@ -415,6 +469,12 @@
            << "Expected number of columns and the column size of Matrix "
            << "to be the reverse of those of Result Type";
   }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot transpose matrices of 16-bit floats";
+  }
   return SPV_SUCCESS;
 }
 
@@ -486,6 +546,36 @@
     }
   }
 
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot shuffle a vector of 8- or 16-bit types";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateCopyLogical(ValidationState_t& _,
+                                 const Instruction* inst) {
+  const auto result_type = _.FindDef(inst->type_id());
+  const auto source = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
+  const auto source_type = _.FindDef(source->type_id());
+  if (!source_type || !result_type || source_type == result_type) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Result Type must not equal the Operand type";
+  }
+
+  if (!_.LogicallyMatch(source_type, result_type, false)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Result Type does not logically match the Operand type";
+  }
+
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Cannot copy composites of 8- or 16-bit types";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -510,6 +600,8 @@
       return ValidateCopyObject(_, inst);
     case SpvOpTranspose:
       return ValidateTranspose(_, inst);
+    case SpvOpCopyLogical:
+      return ValidateCopyLogical(_, inst);
     default:
       break;
   }
diff --git a/source/val/validate_constants.cpp b/source/val/validate_constants.cpp
index e2f20f6..04544aa 100644
--- a/source/val/validate_constants.cpp
+++ b/source/val/validate_constants.cpp
@@ -12,10 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include "source/opcode.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -247,6 +246,36 @@
         }
       }
     } break;
+    case SpvOpTypeCooperativeMatrixNV: {
+      if (1 != constituent_count) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(inst->type_id()) << "' count must be one.";
+      }
+      const auto constituent_id = inst->GetOperandAs<uint32_t>(2);
+      const auto constituent = _.FindDef(constituent_id);
+      if (!constituent || !spvOpcodeIsConstantOrUndef(constituent->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(constituent_id)
+               << "' is not a constant or undef.";
+      }
+      const auto constituent_type = _.FindDef(constituent->type_id());
+      if (!constituent_type) {
+        return _.diag(SPV_ERROR_INVALID_ID, constituent)
+               << "Result type is not defined.";
+      }
+
+      const auto component_type_id = result_type->GetOperandAs<uint32_t>(1);
+      const auto component_type = _.FindDef(component_type_id);
+      if (!component_type || component_type->id() != constituent_type->id()) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(constituent_id)
+               << "' type does not match the Result Type <id> '"
+               << _.getIdName(result_type->id()) << "'s component type.";
+      }
+    } break;
     default:
       break;
   }
@@ -285,6 +314,7 @@
       return true;
     case SpvOpTypeArray:
     case SpvOpTypeMatrix:
+    case SpvOpTypeCooperativeMatrixNV:
     case SpvOpTypeVector: {
       auto base_type = _.FindDef(instruction[2]);
       return base_type && IsTypeNullable(base_type->words(), _);
@@ -320,7 +350,7 @@
 
   // The binary parser already ensures that the op is valid for *some*
   // environment.  Here we check restrictions.
-  switch(op) {
+  switch (op) {
     case SpvOpQuantizeToF16:
       if (!_.HasCapability(SpvCapabilityShader)) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -333,7 +363,8 @@
       if (!_.features().uconvert_spec_constant_op &&
           !_.HasCapability(SpvCapabilityKernel)) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "UConvert requires Kernel capability or extension "
+               << "Prior to SPIR-V 1.4, specialization constant operation "
+                  "UConvert requires Kernel capability or extension "
                   "SPV_AMD_gpu_shader_int16";
       }
       break;
@@ -365,7 +396,7 @@
       }
       break;
 
-  default:
+    default:
       break;
   }
 
@@ -400,6 +431,16 @@
       break;
   }
 
+  // Generally disallow creating 8- or 16-bit constants unless the full
+  // capabilities are present.
+  if (spvOpcodeIsConstant(inst->opcode()) &&
+      _.HasCapability(SpvCapabilityShader) &&
+      !_.IsPointerType(inst->type_id()) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cannot form constants of 8- or 16-bit types";
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp
index 73da582..f7eb881 100644
--- a/source/val/validate_conversion.cpp
+++ b/source/val/validate_conversion.cpp
@@ -32,53 +32,61 @@
   switch (opcode) {
     case SpvOpConvertFToU: {
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !_.IsUnsignedIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
-
-      if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Invalid cast to 8-bit integer from a floating-point: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       break;
     }
 
     case SpvOpConvertFToS: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !_.IsIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
-
-      if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Invalid cast to 8-bit integer from a floating-point: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       break;
     }
@@ -86,49 +94,62 @@
     case SpvOpConvertSToF:
     case SpvOpConvertUToF: {
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !_.IsFloatCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
-
-      if (!_.features().use_int8_type && (8 == _.GetBitWidth(input_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Invalid cast to floating-point from an 8-bit integer: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       break;
     }
 
     case SpvOpUConvert: {
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !_.IsUnsignedIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -139,22 +160,31 @@
     }
 
     case SpvOpSConvert: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !_.IsIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -166,22 +196,31 @@
 
     case SpvOpFConvert: {
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !_.IsFloatCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -455,6 +494,25 @@
       break;
   }
 
+  if (_.HasCapability(SpvCapabilityShader)) {
+    switch (inst->opcode()) {
+      case SpvOpConvertFToU:
+      case SpvOpConvertFToS:
+      case SpvOpConvertSToF:
+      case SpvOpConvertUToF:
+      case SpvOpBitcast:
+        if (_.ContainsLimitedUseIntOrFloatType(inst->type_id()) ||
+            _.ContainsLimitedUseIntOrFloatType(_.GetOperandTypeId(inst, 2u))) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "8- or 16-bit types can only be used with width-only "
+                    "conversions";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_datarules.cpp b/source/val/validate_datarules.cpp
index 129b6bb..826eb8d 100644
--- a/source/val/validate_datarules.cpp
+++ b/source/val/validate_datarules.cpp
@@ -214,6 +214,21 @@
   return SPV_SUCCESS;
 }
 
+// Validates that any undefined type of the array is a forward pointer.
+// It is valid to declare a forward pointer, and use its <id> as the element
+// type of the array.
+spv_result_t ValidateArray(ValidationState_t& _, const Instruction* inst) {
+  auto element_type_id = inst->GetOperandAs<const uint32_t>(1);
+  auto element_type_instruction = _.FindDef(element_type_id);
+  if (element_type_instruction == nullptr &&
+      !_.IsForwardPointer(element_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Forward reference operands in an OpTypeArray must first be "
+              "declared using OpTypeForwardPointer.";
+  }
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 // Validates that Data Rules are followed according to the specifications.
@@ -256,6 +271,10 @@
       if (auto error = ValidateStruct(_, inst)) return error;
       break;
     }
+    case SpvOpTypeArray: {
+      if (auto error = ValidateArray(_, inst)) return error;
+      break;
+    }
     // TODO(ehsan): add more data rules validation here.
     default: { break; }
   }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 7f150aa..3323961 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <cassert>
 #include <string>
@@ -25,8 +23,10 @@
 
 #include "source/diagnostic.h"
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/spirv_validator_options.h"
+#include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -379,10 +379,15 @@
 // or row major-ness.
 spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
                          const char* decoration_str, bool blockRules,
+                         uint32_t incoming_offset,
                          MemberConstraints& constraints,
                          ValidationState_t& vstate) {
   if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
 
+  // blockRules are the same as bufferBlock rules if the uniform buffer
+  // standard layout extension is being used.
+  if (vstate.options()->uniform_buffer_standard_layout) blockRules = false;
+
   // Relaxed layout and scalar layout can both be in effect at the same time.
   // For example, relaxed layout is implied by Vulkan 1.1.  But scalar layout
   // is more permissive than relaxed layout.
@@ -429,7 +434,8 @@
         }
       }
     }
-    member_offsets.push_back(MemberOffsetPair{memberIdx, offset});
+    member_offsets.push_back(
+        MemberOffsetPair{memberIdx, incoming_offset + offset});
   }
   std::stable_sort(
       member_offsets.begin(), member_offsets.end(),
@@ -493,9 +499,9 @@
     // Check struct members recursively.
     spv_result_t recursive_status = SPV_SUCCESS;
     if (SpvOpTypeStruct == opcode &&
-        SPV_SUCCESS != (recursive_status =
-                            checkLayout(id, storage_class_str, decoration_str,
-                                        blockRules, constraints, vstate)))
+        SPV_SUCCESS != (recursive_status = checkLayout(
+                            id, storage_class_str, decoration_str, blockRules,
+                            offset, constraints, vstate)))
       return recursive_status;
     // Check matrix stride.
     if (SpvOpTypeMatrix == opcode) {
@@ -507,23 +513,56 @@
                  << " not satisfying alignment to " << alignment;
       }
     }
-    // Check arrays and runtime arrays.
-    if (SpvOpTypeArray == opcode || SpvOpTypeRuntimeArray == opcode) {
-      const auto typeId = inst->word(2);
-      const auto arrayInst = vstate.FindDef(typeId);
-      if (SpvOpTypeStruct == arrayInst->opcode() &&
-          SPV_SUCCESS != (recursive_status = checkLayout(
-                              typeId, storage_class_str, decoration_str,
-                              blockRules, constraints, vstate)))
-        return recursive_status;
+
+    // Check arrays and runtime arrays recursively.
+    auto array_inst = inst;
+    auto array_alignment = alignment;
+    while (array_inst->opcode() == SpvOpTypeArray ||
+           array_inst->opcode() == SpvOpTypeRuntimeArray) {
+      const auto typeId = array_inst->word(2);
+      const auto element_inst = vstate.FindDef(typeId);
       // Check array stride.
-      for (auto& decoration : vstate.id_decorations(id)) {
-        if (SpvDecorationArrayStride == decoration.dec_type() &&
-            !IsAlignedTo(decoration.params()[0], alignment))
-          return fail(memberIdx)
-                 << "is an array with stride " << decoration.params()[0]
-                 << " not satisfying alignment to " << alignment;
+      auto array_stride = 0;
+      for (auto& decoration : vstate.id_decorations(array_inst->id())) {
+        if (SpvDecorationArrayStride == decoration.dec_type()) {
+          array_stride = decoration.params()[0];
+          if (!IsAlignedTo(array_stride, array_alignment))
+            return fail(memberIdx)
+                   << "contains an array with stride " << decoration.params()[0]
+                   << " not satisfying alignment to " << alignment;
+        }
       }
+
+      bool is_int32 = false;
+      bool is_const = false;
+      uint32_t num_elements = 0;
+      if (array_inst->opcode() == SpvOpTypeArray) {
+        std::tie(is_int32, is_const, num_elements) =
+            vstate.EvalInt32IfConst(array_inst->word(3));
+      }
+      num_elements = std::max(1u, num_elements);
+      // Check each element recursively if it is a struct. There is a
+      // limitation to this check if the array size is a spec constant or is a
+      // runtime array then we will only check a single element. This means
+      // some improper straddles might be missed.
+      for (uint32_t i = 0; i < num_elements; ++i) {
+        uint32_t next_offset = i * array_stride + offset;
+        if (SpvOpTypeStruct == element_inst->opcode() &&
+            SPV_SUCCESS != (recursive_status = checkLayout(
+                                typeId, storage_class_str, decoration_str,
+                                blockRules, next_offset, constraints, vstate)))
+          return recursive_status;
+        // If offsets accumulate up to a 16-byte multiple stop checking since
+        // it will just repeat.
+        if (i > 0 && (next_offset % 16 == 0)) break;
+      }
+
+      // Proceed to the element in case it is an array.
+      array_inst = element_inst;
+      array_alignment = scalar_block_layout
+                            ? getScalarAlignment(array_inst->id(), vstate)
+                            : getBaseAlignment(array_inst->id(), blockRules,
+                                               constraint, constraints, vstate);
     }
     nextValidOffset = offset + size;
     if (!scalar_block_layout && blockRules &&
@@ -649,6 +688,7 @@
     int num_builtin_inputs = 0;
     int num_builtin_outputs = 0;
     for (const auto& desc : descs) {
+      std::unordered_set<Instruction*> seen_vars;
       for (auto interface : desc.interfaces) {
         Instruction* var_instr = vstate.FindDef(interface);
         if (!var_instr || SpvOpVariable != var_instr->opcode()) {
@@ -659,14 +699,30 @@
         }
         const SpvStorageClass storage_class =
             var_instr->GetOperandAs<SpvStorageClass>(2);
-        if (storage_class != SpvStorageClassInput &&
-            storage_class != SpvStorageClassOutput) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
-                 << "OpEntryPoint interfaces must be OpVariables with "
-                    "Storage Class of Input(1) or Output(3). Found Storage "
-                    "Class "
-                 << storage_class << " for Entry Point id " << entry_point
-                 << ".";
+        if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+          // Starting in 1.4, OpEntryPoint must list all global variables
+          // it statically uses and those interfaces must be unique.
+          if (storage_class == SpvStorageClassFunction) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "OpEntryPoint interfaces should only list global "
+                      "variables";
+          }
+
+          if (!seen_vars.insert(var_instr).second) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "Non-unique OpEntryPoint interface "
+                   << vstate.getIdName(interface) << " is disallowed";
+          }
+        } else {
+          if (storage_class != SpvStorageClassInput &&
+              storage_class != SpvStorageClassOutput) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "OpEntryPoint interfaces must be OpVariables with "
+                      "Storage Class of Input(1) or Output(3). Found Storage "
+                      "Class "
+                   << storage_class << " for Entry Point id " << entry_point
+                   << ".";
+          }
         }
 
         const uint32_t ptr_id = var_instr->word(1);
@@ -854,13 +910,39 @@
         }
       }
 
+      if (spvIsOpenGLEnv(vstate.context()->target_env)) {
+        bool has_block = hasDecoration(var_id, SpvDecorationBlock, vstate);
+        bool has_buffer_block =
+            hasDecoration(var_id, SpvDecorationBufferBlock, vstate);
+        if ((uniform && (has_block || has_buffer_block)) ||
+            (storage_buffer && has_block)) {
+          auto entry_points = vstate.EntryPointReferences(var_id);
+          if (!entry_points.empty() &&
+              !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                   << (uniform ? "Uniform" : "Storage Buffer") << " id '"
+                   << var_id << "' is missing Binding decoration.\n"
+                   << "From ARB_gl_spirv extension:\n"
+                   << "Uniform and shader storage block variables must "
+                   << "also be decorated with a *Binding*.";
+          }
+        }
+      }
+
       const bool phys_storage_buffer =
           storageClass == SpvStorageClassPhysicalStorageBufferEXT;
       if (uniform || push_constant || storage_buffer || phys_storage_buffer) {
         const auto ptrInst = vstate.FindDef(words[1]);
         assert(SpvOpTypePointer == ptrInst->opcode());
-        const auto id = ptrInst->words()[3];
-        if (SpvOpTypeStruct != vstate.FindDef(id)->opcode()) continue;
+        auto id = ptrInst->words()[3];
+        auto id_inst = vstate.FindDef(id);
+        // Jump through one level of arraying.
+        if (id_inst->opcode() == SpvOpTypeArray ||
+            id_inst->opcode() == SpvOpTypeRuntimeArray) {
+          id = id_inst->GetOperandAs<uint32_t>(1u);
+          id_inst = vstate.FindDef(id);
+        }
+        if (SpvOpTypeStruct != id_inst->opcode()) continue;
         MemberConstraints constraints;
         ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
                                           vstate);
@@ -870,6 +952,13 @@
                     : (push_constant ? "PushConstant" : "StorageBuffer");
 
         if (spvIsVulkanEnv(vstate.context()->target_env)) {
+          if (storage_buffer &&
+              hasDecoration(id, SpvDecorationBufferBlock, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                   << "Storage buffer id '" << var_id
+                   << " In Vulkan, BufferBlock is disallowed on variables in "
+                      "the StorageBuffer storage class";
+          }
           // Vulkan 14.5.1: Check Block decoration for PushConstant variables.
           if (push_constant && !hasDecoration(id, SpvDecorationBlock, vstate)) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
@@ -911,6 +1000,16 @@
           const bool bufferRules =
               (uniform && bufferDeco) || (push_constant && blockDeco) ||
               ((storage_buffer || phys_storage_buffer) && blockDeco);
+          if (uniform && blockDeco) {
+            vstate.RegisterPointerToUniformBlock(ptrInst->id());
+            vstate.RegisterStructForUniformBlock(id);
+          }
+          if ((uniform && bufferDeco) ||
+              ((storage_buffer || phys_storage_buffer) && blockDeco)) {
+            vstate.RegisterPointerToStorageBuffer(ptrInst->id());
+            vstate.RegisterStructForStorageBuffer(id);
+          }
+
           if (blockRules || bufferRules) {
             const char* deco_str = blockDeco ? "Block" : "BufferBlock";
             spv_result_t recursive_status = SPV_SUCCESS;
@@ -942,12 +1041,12 @@
                         "decorations.";
             } else if (blockRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, true,
+                                            id, sc_str, deco_str, true, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             } else if (bufferRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, false,
+                                            id, sc_str, deco_str, false, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             }
@@ -959,42 +1058,69 @@
   return SPV_SUCCESS;
 }
 
+// Returns true if |decoration| cannot be applied to the same id more than once.
+bool AtMostOncePerId(SpvDecoration decoration) {
+  return decoration == SpvDecorationArrayStride;
+}
+
+// Returns true if |decoration| cannot be applied to the same member more than
+// once.
+bool AtMostOncePerMember(SpvDecoration decoration) {
+  switch (decoration) {
+    case SpvDecorationOffset:
+    case SpvDecorationMatrixStride:
+    case SpvDecorationRowMajor:
+    case SpvDecorationColMajor:
+      return true;
+    default:
+      return false;
+  }
+}
+
+// Returns the string name for |decoration|.
+const char* GetDecorationName(SpvDecoration decoration) {
+  switch (decoration) {
+    case SpvDecorationAliased:
+      return "Aliased";
+    case SpvDecorationRestrict:
+      return "Restrict";
+    case SpvDecorationArrayStride:
+      return "ArrayStride";
+    case SpvDecorationOffset:
+      return "Offset";
+    case SpvDecorationMatrixStride:
+      return "MatrixStride";
+    case SpvDecorationRowMajor:
+      return "RowMajor";
+    case SpvDecorationColMajor:
+      return "ColMajor";
+    case SpvDecorationBlock:
+      return "Block";
+    case SpvDecorationBufferBlock:
+      return "BufferBlock";
+    default:
+      return "";
+  }
+}
+
 spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
-  using AtMostOnceSet = std::unordered_set<SpvDecoration, SpvDecorationHash>;
-  using MutuallyExclusiveSets =
-      std::vector<std::unordered_set<SpvDecoration, SpvDecorationHash>>;
   using PerIDKey = std::tuple<SpvDecoration, uint32_t>;
   using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
-  using DecorationNameTable =
-      std::unordered_map<SpvDecoration, std::string, SpvDecorationHash>;
 
-  static const auto* const at_most_once_per_id = new AtMostOnceSet{
-      SpvDecorationArrayStride,
-  };
-  static const auto* const at_most_once_per_member = new AtMostOnceSet{
-      SpvDecorationOffset,
-      SpvDecorationMatrixStride,
-      SpvDecorationRowMajor,
-      SpvDecorationColMajor,
-  };
-  static const auto* const mutually_exclusive_per_id =
-      new MutuallyExclusiveSets{
-          {SpvDecorationBlock, SpvDecorationBufferBlock},
-      };
-  static const auto* const mutually_exclusive_per_member =
-      new MutuallyExclusiveSets{
-          {SpvDecorationRowMajor, SpvDecorationColMajor},
-      };
-  // For printing the decoration name.
-  static const auto* const decoration_name = new DecorationNameTable{
-      {SpvDecorationArrayStride, "ArrayStride"},
-      {SpvDecorationOffset, "Offset"},
-      {SpvDecorationMatrixStride, "MatrixStride"},
-      {SpvDecorationRowMajor, "RowMajor"},
-      {SpvDecorationColMajor, "ColMajor"},
-      {SpvDecorationBlock, "Block"},
-      {SpvDecorationBufferBlock, "BufferBlock"},
-  };
+  // An Array of pairs where the decorations in the pair cannot both be applied
+  // to the same id.
+  static const SpvDecoration mutually_exclusive_per_id[][2] = {
+      {SpvDecorationBlock, SpvDecorationBufferBlock},
+      {SpvDecorationRestrict, SpvDecorationAliased}};
+  static const auto num_mutually_exclusive_per_id_pairs =
+      sizeof(mutually_exclusive_per_id) / (2 * sizeof(SpvDecoration));
+
+  // An Array of pairs where the decorations in the pair cannot both be applied
+  // to the same member.
+  static const SpvDecoration mutually_exclusive_per_member[][2] = {
+      {SpvDecorationRowMajor, SpvDecorationColMajor}};
+  static const auto num_mutually_exclusive_per_mem_pairs =
+      sizeof(mutually_exclusive_per_member) / (2 * sizeof(SpvDecoration));
 
   std::set<PerIDKey> seen_per_id;
   std::set<PerMemberKey> seen_per_member;
@@ -1006,26 +1132,31 @@
       const auto dec_type = static_cast<SpvDecoration>(words[2]);
       const auto k = PerIDKey(dec_type, id);
       const auto already_used = !seen_per_id.insert(k).second;
-      if (already_used &&
-          at_most_once_per_id->find(dec_type) != at_most_once_per_id->end()) {
+      if (already_used && AtMostOncePerId(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "' decorated with "
-               << decoration_name->at(dec_type)
+               << GetDecorationName(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
       // an ID.
-      for (const auto& s : *mutually_exclusive_per_id) {
-        if (s.find(dec_type) == s.end()) continue;
-        for (auto excl_dec_type : s) {
-          if (excl_dec_type == dec_type) continue;
-          const auto excl_k = PerIDKey(excl_dec_type, id);
-          if (seen_per_id.find(excl_k) != seen_per_id.end()) {
-            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "ID '" << id << "' decorated with both "
-                   << decoration_name->at(dec_type) << " and "
-                   << decoration_name->at(excl_dec_type) << " is not allowed.";
-          }
+      for (uint32_t pair_idx = 0;
+           pair_idx < num_mutually_exclusive_per_id_pairs; ++pair_idx) {
+        SpvDecoration excl_dec_type = SpvDecorationMax;
+        if (mutually_exclusive_per_id[pair_idx][0] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_id[pair_idx][1];
+        } else if (mutually_exclusive_per_id[pair_idx][1] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_id[pair_idx][0];
+        } else {
+          continue;
+        }
+
+        const auto excl_k = PerIDKey(excl_dec_type, id);
+        if (seen_per_id.find(excl_k) != seen_per_id.end()) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                 << "ID '" << id << "' decorated with both "
+                 << GetDecorationName(dec_type) << " and "
+                 << GetDecorationName(excl_dec_type) << " is not allowed.";
         }
       }
     } else if (SpvOpMemberDecorate == inst.opcode()) {
@@ -1034,27 +1165,32 @@
       const auto dec_type = static_cast<SpvDecoration>(words[3]);
       const auto k = PerMemberKey(dec_type, id, member_id);
       const auto already_used = !seen_per_member.insert(k).second;
-      if (already_used && at_most_once_per_member->find(dec_type) !=
-                              at_most_once_per_member->end()) {
+      if (already_used && AtMostOncePerMember(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "', member '" << member_id
-               << "' decorated with " << decoration_name->at(dec_type)
+               << "' decorated with " << GetDecorationName(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
       // a (ID, member) tuple.
-      for (const auto& s : *mutually_exclusive_per_member) {
-        if (s.find(dec_type) == s.end()) continue;
-        for (auto excl_dec_type : s) {
-          if (excl_dec_type == dec_type) continue;
-          const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
-          if (seen_per_member.find(excl_k) != seen_per_member.end()) {
-            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "ID '" << id << "', member '" << member_id
-                   << "' decorated with both " << decoration_name->at(dec_type)
-                   << " and " << decoration_name->at(excl_dec_type)
-                   << " is not allowed.";
-          }
+      for (uint32_t pair_idx = 0;
+           pair_idx < num_mutually_exclusive_per_mem_pairs; ++pair_idx) {
+        SpvDecoration excl_dec_type = SpvDecorationMax;
+        if (mutually_exclusive_per_member[pair_idx][0] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_member[pair_idx][1];
+        } else if (mutually_exclusive_per_member[pair_idx][1] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_member[pair_idx][0];
+        } else {
+          continue;
+        }
+
+        const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
+        if (seen_per_member.find(excl_k) != seen_per_member.end()) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                 << "ID '" << id << "', member '" << member_id
+                 << "' decorated with both " << GetDecorationName(dec_type)
+                 << " and " << GetDecorationName(excl_dec_type)
+                 << " is not allowed.";
         }
       }
     }
@@ -1106,6 +1242,9 @@
   // Validates Object operand of an OpStore
   for (const auto& use : inst.uses()) {
     const auto store = use.first;
+    if (store->opcode() == SpvOpFConvert) continue;
+    if (spvOpcodeIsDebug(store->opcode())) continue;
+    if (spvOpcodeIsDecoration(store->opcode())) continue;
     if (store->opcode() != SpvOpStore) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
              << "FPRoundingMode decoration can be applied only to the "
@@ -1148,14 +1287,63 @@
   return SPV_SUCCESS;
 }
 
-// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
-// decorations. Otherwise emits a diagnostic and returns something other than
-// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
-// the group members.
+// Returns SPV_SUCCESS if validation rules are satisfied for the NonWritable
+// decoration.  Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.  The |inst| parameter is the object being decorated.  This must
+// be called after TypePass and AnnotateCheckDecorationsOfBuffers are called.
+spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
+                                        const Instruction& inst,
+                                        const Decoration& decoration) {
+  assert(inst.id() && "Parser ensures the target of the decoration has an ID");
+
+  if (decoration.struct_member_index() == Decoration::kInvalidMember) {
+    // The target must be a memory object declaration.
+    // First, it must be a variable or function parameter.
+    const auto opcode = inst.opcode();
+    const auto type_id = inst.type_id();
+    if (opcode != SpvOpVariable && opcode != SpvOpFunctionParameter) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of NonWritable decoration must be a memory object "
+                "declaration (a variable or a function parameter)";
+    }
+    const auto var_storage_class = opcode == SpvOpVariable
+                                       ? inst.GetOperandAs<SpvStorageClass>(2)
+                                       : SpvStorageClassMax;
+    if ((var_storage_class == SpvStorageClassFunction ||
+         var_storage_class == SpvStorageClassPrivate) &&
+        vstate.features().nonwritable_var_in_function_or_private) {
+      // New permitted feature in SPIR-V 1.4.
+    } else if (
+        // It may point to a UBO, SSBO, or storage image.
+        vstate.IsPointerToUniformBlock(type_id) ||
+        vstate.IsPointerToStorageBuffer(type_id) ||
+        vstate.IsPointerToStorageImage(type_id)) {
+    } else {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of NonWritable decoration is invalid: must point to a "
+                "storage image, uniform block, "
+             << (vstate.features().nonwritable_var_in_function_or_private
+                     ? "storage buffer, or variable in Private or Function "
+                       "storage class"
+                     : "or storage buffer");
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+// Returns SPV_SUCCESS if validation rules are satisfied for Uniform or
+// UniformId decorations. Otherwise emits a diagnostic and returns something
+// other than SPV_SUCCESS. Assumes each decoration on a group has been
+// propagated down to the group members.  The |inst| parameter is the object
+// being decorated.
 spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
                                     const Instruction& inst,
-                                    const Decoration&) {
-  // Uniform must decorate an "object"
+                                    const Decoration& decoration) {
+  const char* const dec_name =
+      decoration.dec_type() == SpvDecorationUniform ? "Uniform" : "UniformId";
+
+  // Uniform or UniformId must decorate an "object"
   //  - has a result ID
   //  - is an instantiation of a non-void type.  So it has a type ID, and that
   //  type is not void.
@@ -1164,19 +1352,33 @@
 
   if (inst.type_id() == 0) {
     return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-           << "Uniform decoration applied to a non-object";
+           << dec_name << " decoration applied to a non-object";
   }
   if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
     if (type_inst->opcode() == SpvOpTypeVoid) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-             << "Uniform decoration applied to a value with void type";
+             << dec_name << " decoration applied to a value with void type";
     }
   } else {
     // We might never get here because this would have been rejected earlier in
     // the flow.
     return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-           << "Uniform decoration applied to an object with invalid type";
+           << dec_name << " decoration applied to an object with invalid type";
   }
+
+  // Use of Uniform with OpDecorate is checked elsewhere.
+  // Use of UniformId with OpDecorateId is checked elsewhere.
+
+  if (decoration.dec_type() == SpvDecorationUniformId) {
+    assert(decoration.params().size() == 1 &&
+           "Grammar ensures UniformId has one parameter");
+
+    // The scope id is an execution scope.
+    if (auto error =
+            ValidateExecutionScope(vstate, &inst, decoration.params()[0]))
+      return error;
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -1210,6 +1412,86 @@
          << spvOpcodeString(inst.opcode());
 }
 
+// Returns SPV_SUCCESS if validation rules are satisfied for the Component
+// decoration.  Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.
+spv_result_t CheckComponentDecoration(ValidationState_t& vstate,
+                                      const Instruction& inst,
+                                      const Decoration& decoration) {
+  assert(inst.id() && "Parser ensures the target of the decoration has an ID");
+
+  uint32_t type_id;
+  if (decoration.struct_member_index() == Decoration::kInvalidMember) {
+    // The target must be a memory object declaration.
+    const auto opcode = inst.opcode();
+    if (opcode != SpvOpVariable && opcode != SpvOpFunctionParameter) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of Component decoration must be a memory object "
+                "declaration (a variable or a function parameter)";
+    }
+
+    // Only valid for the Input and Output Storage Classes.
+    const auto storage_class = opcode == SpvOpVariable
+                                   ? inst.GetOperandAs<SpvStorageClass>(2)
+                                   : SpvStorageClassMax;
+    if (storage_class != SpvStorageClassInput &&
+        storage_class != SpvStorageClassOutput &&
+        storage_class != SpvStorageClassMax) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of Component decoration is invalid: must point to a "
+                "Storage Class of Input(1) or Output(3). Found Storage "
+                "Class "
+             << storage_class;
+    }
+
+    type_id = inst.type_id();
+    if (vstate.IsPointerType(type_id)) {
+      const auto pointer = vstate.FindDef(type_id);
+      type_id = pointer->GetOperandAs<uint32_t>(2);
+    }
+  } else {
+    if (inst.opcode() != SpvOpTypeStruct) {
+      return vstate.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << "Attempted to get underlying data type via member index for "
+                "non-struct type.";
+    }
+    type_id = inst.word(decoration.struct_member_index() + 2);
+  }
+
+  if (spvIsVulkanEnv(vstate.context()->target_env)) {
+    // Strip the array, if present.
+    if (vstate.GetIdOpcode(type_id) == SpvOpTypeArray) {
+      type_id = vstate.FindDef(type_id)->word(2u);
+    }
+
+    if (!vstate.IsIntScalarOrVectorType(type_id) &&
+        !vstate.IsFloatScalarOrVectorType(type_id)) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Component decoration specified for type "
+             << vstate.getIdName(type_id) << " that is not a scalar or vector";
+    }
+
+    // For 16-, and 32-bit types, it is invalid if this sequence of components
+    // gets larger than 3.
+    const auto bit_width = vstate.GetBitWidth(type_id);
+    if (bit_width == 16 || bit_width == 32) {
+      assert(decoration.params().size() == 1 &&
+             "Grammar ensures Component has one parameter");
+
+      const auto component = decoration.params()[0];
+      const auto last_component = component + vstate.GetDimension(type_id) - 1;
+      if (last_component > 3) {
+        return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+               << "Sequence of components starting with " << component
+               << " and ending with " << last_component
+               << " gets larger than 3";
+      }
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1236,14 +1518,20 @@
     // been propagated down to the group members.
     if (inst->opcode() == SpvOpDecorationGroup) continue;
 
-    // Validates FPRoundingMode decoration
     for (const auto& decoration : decorations) {
       switch (decoration.dec_type()) {
+        case SpvDecorationComponent:
+          PASS_OR_BAIL(CheckComponentDecoration(vstate, *inst, decoration));
+          break;
         case SpvDecorationFPRoundingMode:
           if (is_shader)
             PASS_OR_BAIL(CheckFPRoundingModeForShaders(vstate, *inst));
           break;
+        case SpvDecorationNonWritable:
+          PASS_OR_BAIL(CheckNonWritableDecoration(vstate, *inst, decoration));
+          break;
         case SpvDecorationUniform:
+        case SpvDecorationUniformId:
           PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
           break;
         case SpvDecorationNoSignedWrap:
diff --git a/source/val/validate_derivatives.cpp b/source/val/validate_derivatives.cpp
index 7189707..b3f046a 100644
--- a/source/val/validate_derivatives.cpp
+++ b/source/val/validate_derivatives.cpp
@@ -53,37 +53,45 @@
                << "Expected P type and Result Type to be the same: "
                << spvOpcodeString(opcode);
       }
-
-      const spvtools::Extension compute_shader_derivatives_extension =
-          kSPV_NV_compute_shader_derivatives;
-      ExtensionSet exts(1, &compute_shader_derivatives_extension);
-
-      if (_.HasAnyOfExtensions(exts)) {
-        _.function(inst->function()->id())
-            ->RegisterExecutionModelLimitation([opcode](SpvExecutionModel model,
-                                                        std::string* message) {
-              if (model != SpvExecutionModelFragment &&
-                  model != SpvExecutionModelGLCompute) {
-                if (message) {
-                  *message =
-                      std::string(
-                          "Derivative instructions require Fragment execution "
-                          "model: ") +
-                      spvOpcodeString(opcode);
-                }
-                return false;
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation([opcode](SpvExecutionModel model,
+                                                      std::string* message) {
+            if (model != SpvExecutionModelFragment &&
+                model != SpvExecutionModelGLCompute) {
+              if (message) {
+                *message =
+                    std::string(
+                        "Derivative instructions require Fragment or GLCompute "
+                        "execution model: ") +
+                    spvOpcodeString(opcode);
               }
-              return true;
-            });
-      } else {
-        _.function(inst->function()->id())
-            ->RegisterExecutionModelLimitation(
-                SpvExecutionModelFragment,
-                std::string(
-                    "Derivative instructions require Fragment execution "
-                    "model: ") +
-                    spvOpcodeString(opcode));
-      }
+              return false;
+            }
+            return true;
+          });
+      _.function(inst->function()->id())
+          ->RegisterLimitation([opcode](const ValidationState_t& state,
+                                        const Function* entry_point,
+                                        std::string* message) {
+            const auto* models = state.GetExecutionModels(entry_point->id());
+            const auto* modes = state.GetExecutionModes(entry_point->id());
+            if (models->find(SpvExecutionModelGLCompute) != models->end() &&
+                modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
+                    modes->end() &&
+                modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
+                    modes->end()) {
+              if (message) {
+                *message = std::string(
+                               "Derivative instructions require "
+                               "DerivativeGroupQuadsNV "
+                               "or DerivativeGroupLinearNV execution mode for "
+                               "GLCompute execution model: ") +
+                           spvOpcodeString(opcode);
+              }
+              return false;
+            }
+            return true;
+          });
       break;
     }
 
diff --git a/source/val/validate_execution_limitations.cpp b/source/val/validate_execution_limitations.cpp
index d449307..aac1c49 100644
--- a/source/val/validate_execution_limitations.cpp
+++ b/source/val/validate_execution_limitations.cpp
@@ -53,6 +53,17 @@
         }
       }
     }
+
+    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())
+             << ", which cannot be used with the current execution "
+                "modes:\n"
+             << reason;
+    }
   }
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index f264c8e..ec769db 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -1547,11 +1547,14 @@
         }
 
         if (p_storage_class != SpvStorageClassUniformConstant &&
-            p_storage_class != SpvStorageClassGeneric) {
+            p_storage_class != SpvStorageClassGeneric &&
+            p_storage_class != SpvStorageClassCrossWorkgroup &&
+            p_storage_class != SpvStorageClassWorkgroup &&
+            p_storage_class != SpvStorageClassFunction) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << ext_inst_name() << ": "
-                 << "expected operand P storage class to be UniformConstant or "
-                    "Generic";
+                 << "expected operand P storage class to be UniformConstant, "
+                    "Generic, CrossWorkgroup, Workgroup or Function";
         }
 
         if (_.GetComponentType(result_type) != p_data_type) {
@@ -1618,10 +1621,14 @@
                  << "expected operand P to be a pointer";
         }
 
-        if (p_storage_class != SpvStorageClassGeneric) {
+        if (p_storage_class != SpvStorageClassGeneric &&
+            p_storage_class != SpvStorageClassCrossWorkgroup &&
+            p_storage_class != SpvStorageClassWorkgroup &&
+            p_storage_class != SpvStorageClassFunction) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << ext_inst_name() << ": "
-                 << "expected operand P storage class to be Generic";
+                 << "expected operand P storage class to be Generic, "
+                    "CrossWorkgroup, Workgroup or Function";
         }
 
         if (_.GetComponentType(data_type) != p_data_type) {
diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp
index de41b27..b983194 100644
--- a/source/val/validate_function.cpp
+++ b/source/val/validate_function.cpp
@@ -12,18 +12,47 @@
 // 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/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+// Returns true if |a| and |b| are instructions defining pointers that point to
+// types logically match and the decorations that apply to |b| are a subset
+// of the decorations that apply to |a|.
+bool DoPointeesLogicallyMatch(val::Instruction* a, val::Instruction* b,
+                              ValidationState_t& _) {
+  if (a->opcode() != SpvOpTypePointer || b->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  const auto& dec_a = _.id_decorations(a->id());
+  const auto& dec_b = _.id_decorations(b->id());
+  for (const auto& dec : dec_b) {
+    if (std::find(dec_a.begin(), dec_a.end(), dec) == dec_a.end()) {
+      return false;
+    }
+  }
+
+  uint32_t a_type = a->GetOperandAs<uint32_t>(2);
+  uint32_t b_type = b->GetOperandAs<uint32_t>(2);
+
+  if (a_type == b_type) {
+    return true;
+  }
+
+  Instruction* a_type_inst = _.FindDef(a_type);
+  Instruction* b_type_inst = _.FindDef(b_type);
+
+  return _.LogicallyMatch(a_type_inst, b_type_inst, true);
+}
+
 spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
   const auto function_type_id = inst->GetOperandAs<uint32_t>(3);
   const auto function_type = _.FindDef(function_type_id);
@@ -41,18 +70,22 @@
            << _.getIdName(return_id) << "'.";
   }
 
+  const std::vector<SpvOp> acceptable = {
+      SpvOpDecorate,
+      SpvOpEnqueueKernel,
+      SpvOpEntryPoint,
+      SpvOpExecutionMode,
+      SpvOpExecutionModeId,
+      SpvOpFunctionCall,
+      SpvOpGetKernelNDrangeSubGroupCount,
+      SpvOpGetKernelNDrangeMaxSubGroupSize,
+      SpvOpGetKernelWorkGroupSize,
+      SpvOpGetKernelPreferredWorkGroupSizeMultiple,
+      SpvOpGetKernelLocalSizeForSubgroupCount,
+      SpvOpGetKernelMaxNumSubgroups,
+      SpvOpName};
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
-    const std::vector<SpvOp> acceptable = {
-        SpvOpFunctionCall,
-        SpvOpEntryPoint,
-        SpvOpEnqueueKernel,
-        SpvOpGetKernelNDrangeSubGroupCount,
-        SpvOpGetKernelNDrangeMaxSubGroupSize,
-        SpvOpGetKernelWorkGroupSize,
-        SpvOpGetKernelPreferredWorkGroupSizeMultiple,
-        SpvOpGetKernelLocalSizeForSubgroupCount,
-        SpvOpGetKernelMaxNumSubgroups};
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
         acceptable.end()) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
@@ -242,10 +275,58 @@
         function_type->GetOperandAs<uint32_t>(param_index);
     const auto parameter_type = _.FindDef(parameter_type_id);
     if (!parameter_type || argument_type->id() != parameter_type->id()) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpFunctionCall Argument <id> '" << _.getIdName(argument_id)
-             << "'s type does not match Function <id> '"
-             << _.getIdName(parameter_type_id) << "'s parameter type.";
+      if (!_.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.";
+      }
+    }
+
+    if (_.addressing_model() == SpvAddressingModelLogical) {
+      if (parameter_type->opcode() == SpvOpTypePointer &&
+          !_.options()->relax_logical_pointer) {
+        SpvStorageClass sc = parameter_type->GetOperandAs<SpvStorageClass>(1u);
+        // Validate which storage classes can be pointer operands.
+        switch (sc) {
+          case SpvStorageClassUniformConstant:
+          case SpvStorageClassFunction:
+          case SpvStorageClassPrivate:
+          case SpvStorageClassWorkgroup:
+          case SpvStorageClassAtomicCounter:
+            // These are always allowed.
+            break;
+          case SpvStorageClassStorageBuffer:
+            if (!_.features().variable_pointers_storage_buffer) {
+              return _.diag(SPV_ERROR_INVALID_ID, inst)
+                     << "StorageBuffer pointer operand "
+                     << _.getIdName(argument_id)
+                     << " requires a variable pointers capability";
+            }
+            break;
+          default:
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Invalid storage class for pointer operand "
+                   << _.getIdName(argument_id);
+        }
+
+        // Validate memory object declaration requirements.
+        if (argument->opcode() != SpvOpVariable &&
+            argument->opcode() != SpvOpFunctionParameter) {
+          const bool ssbo_vptr =
+              _.features().variable_pointers_storage_buffer &&
+              sc == SpvStorageClassStorageBuffer;
+          const bool wg_vptr =
+              _.features().variable_pointers && sc == SpvStorageClassWorkgroup;
+          const bool uc_ptr = sc == SpvStorageClassUniformConstant;
+          if (!ssbo_vptr && !wg_vptr && !uc_ptr) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Pointer operand " << _.getIdName(argument_id)
+                   << " must be a memory object declaration";
+          }
+        }
+      }
     }
   }
   return SPV_SUCCESS;
diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp
index 21a0411..cb18e13 100644
--- a/source/val/validate_id.cpp
+++ b/source/val/validate_id.cpp
@@ -167,7 +167,10 @@
           const auto opcode = inst->opcode();
           if (spvOpcodeGeneratesType(def->opcode()) &&
               !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
-              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction) {
+              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction &&
+              opcode != SpvOpCooperativeMatrixLengthNV &&
+              !(opcode == SpvOpSpecConstantOp &&
+                inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "Operand " << _.getIdName(operand_word)
                    << " cannot be a type";
@@ -177,7 +180,10 @@
                      !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi &&
                      opcode != SpvOpExtInst && opcode != SpvOpExtInstImport &&
                      opcode != SpvOpSelectionMerge &&
-                     opcode != SpvOpLoopMerge && opcode != SpvOpFunction) {
+                     opcode != SpvOpLoopMerge && opcode != SpvOpFunction &&
+                     opcode != SpvOpCooperativeMatrixLengthNV &&
+                     !(opcode == SpvOpSpecConstantOp &&
+                       inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "Operand " << _.getIdName(operand_word)
                    << " requires a type";
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 8a357ff..bc2753c 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -63,6 +63,8 @@
     case SpvImageOperandsMakeTexelVisibleKHRMask:
     case SpvImageOperandsNonPrivateTexelKHRMask:
     case SpvImageOperandsVolatileTexelKHRMask:
+    case SpvImageOperandsSignExtendMask:
+    case SpvImageOperandsZeroExtendMask:
       return true;
   }
   return false;
@@ -218,10 +220,12 @@
   const SpvOp opcode = inst->opcode();
   const size_t num_words = inst->words().size();
 
-  // NonPrivate and Volatile take no operand words.
+  // NonPrivate, Volatile, SignExtend, ZeroExtend take no operand words.
   const uint32_t mask_bits_having_operands =
       mask & ~uint32_t(SpvImageOperandsNonPrivateTexelKHRMask |
-                       SpvImageOperandsVolatileTexelKHRMask);
+                       SpvImageOperandsVolatileTexelKHRMask |
+                       SpvImageOperandsSignExtendMask |
+                       SpvImageOperandsZeroExtendMask);
   size_t expected_num_image_operand_words =
       spvtools::utils::CountSetBits(mask_bits_having_operands);
   if (mask & SpvImageOperandsGradMask) {
@@ -541,6 +545,32 @@
     if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
+  if (mask & SpvImageOperandsSignExtendMask) {
+    // Checked elsewhere: SPIR-V 1.4 version or later.
+
+    // "The texel value is converted to the target value via sign extension.
+    // Only valid when the texel type is a scalar or vector of integer type."
+    //
+    // We don't have enough information to know what the texel type is.
+    // In OpenCL, knowledge is deferred until runtime: the image SampledType is
+    // void, and the Format is Unknown.
+    // In Vulkan, the texel type is only known in all cases by the pipeline
+    // setup.
+  }
+
+  if (mask & SpvImageOperandsZeroExtendMask) {
+    // Checked elsewhere: SPIR-V 1.4 version or later.
+
+    // "The texel value is converted to the target value via zero extension.
+    // Only valid when the texel type is a scalar or vector of integer type."
+    //
+    // We don't have enough information to know what the texel type is.
+    // In OpenCL, knowledge is deferred until runtime: the image SampledType is
+    // void, and the Format is Unknown.
+    // In Vulkan, the texel type is only known in all cases by the pipeline
+    // setup.
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -688,6 +718,11 @@
              << "Expected Sampled Type to be a 32-bit int or float "
                 "scalar type for Vulkan environment";
     }
+  } else if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (!_.IsVoidType(info.sampled_type)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Sampled Type must be OpTypeVoid in the OpenCL environment.";
+    }
   } else {
     const SpvOp sampled_type_opcode = _.GetIdOpcode(info.sampled_type);
     if (sampled_type_opcode != SpvOpTypeVoid &&
@@ -711,16 +746,39 @@
            << "Invalid Arrayed " << info.arrayed << " (must be 0 or 1)";
   }
 
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if ((info.arrayed == 1) && (info.dim != SpvDim1D) &&
+        (info.dim != SpvDim2D)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "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(_.context()->target_env)) {
+    if (info.multisampled != 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "MS must be 0 in the OpenCL environement.";
+    }
+  }
+
   if (info.sampled > 2) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Invalid Sampled " << info.sampled << " (must be 0, 1 or 2)";
   }
 
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (info.sampled != 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)
@@ -733,7 +791,15 @@
     }
   }
 
-  // Format and Access Qualifier are checked elsewhere.
+  // 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.";
+    }
+  }
 
   return SPV_SUCCESS;
 }
@@ -748,6 +814,33 @@
   return SPV_SUCCESS;
 }
 
+bool IsAllowedSampledImageOperand(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpSampledImage:
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImage:
+    case SpvOpImageQueryLod:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+      return true;
+    default:
+      return false;
+  }
+}
+
 spv_result_t ValidateSampledImage(ValidationState_t& _,
                                   const Instruction* inst) {
   if (_.GetIdOpcode(inst->type_id()) != SpvOpTypeSampledImage) {
@@ -800,10 +893,9 @@
   // to OpPhi instructions or OpSelect instructions, or any instructions other
   // than the image lookup and query instructions specified to take an operand
   // whose type is OpTypeSampledImage.
-  std::vector<uint32_t> consumers = _.getSampledImageConsumers(inst->id());
+  std::vector<Instruction*> consumers = _.getSampledImageConsumers(inst->id());
   if (!consumers.empty()) {
-    for (auto consumer_id : consumers) {
-      const auto consumer_instr = _.FindDef(consumer_id);
+    for (auto consumer_instr : consumers) {
       const auto consumer_opcode = consumer_instr->opcode();
       if (consumer_instr->block() != inst->block()) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -814,13 +906,9 @@
                << _.getIdName(inst->id())
                << "' has a consumer in a different basic "
                   "block. The consumer instruction <id> is '"
-               << _.getIdName(consumer_id) << "'.";
+               << _.getIdName(consumer_instr->id()) << "'.";
       }
-      // TODO: The following check is incomplete. We should also check that the
-      // Sampled Image is not used by instructions that should not take
-      // SampledImage as an argument. We could find the list of valid
-      // instructions by scanning for "Sampled Image" in the operand description
-      // field in the grammar file.
+
       if (consumer_opcode == SpvOpPhi || consumer_opcode == SpvOpSelect) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "Result <id> from OpSampledImage instruction must not appear "
@@ -828,8 +916,20 @@
                   "operands of Op"
                << spvOpcodeString(static_cast<SpvOp>(consumer_opcode)) << "."
                << " Found result <id> '" << _.getIdName(inst->id())
-               << "' as an operand of <id> '" << _.getIdName(consumer_id)
-               << "'.";
+               << "' as an operand of <id> '"
+               << _.getIdName(consumer_instr->id()) << "'.";
+      }
+
+      if (!IsAllowedSampledImageOperand(consumer_opcode)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Result <id> from OpSampledImage instruction must not appear "
+                  "as operand for Op"
+               << spvOpcodeString(static_cast<SpvOp>(consumer_opcode))
+               << ", since it is not specificed as taking an "
+               << "OpTypeSampledImage."
+               << " Found result <id> '" << _.getIdName(inst->id())
+               << "' as an operand of <id> '"
+               << _.getIdName(consumer_instr->id()) << "'.";
       }
     }
   }
@@ -1014,6 +1114,17 @@
   }
 
   const uint32_t mask = inst->word(5);
+
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (opcode == SpvOpImageSampleExplicitLod) {
+      if (mask & SpvImageOperandsConstOffsetMask) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "ConstOffset image operand not allowed "
+               << "in the OpenCL environment.";
+      }
+    }
+  }
+
   if (spv_result_t result =
           ValidateImageOperands(_, inst, info, mask, /* word_index = */ 6))
     return result;
@@ -1328,16 +1439,27 @@
            << " components, but given only " << actual_coord_size;
   }
 
-  if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
-      !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Capability StorageImageReadWithoutFormat is required to "
-           << "read storage image";
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
+        !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageReadWithoutFormat is required to "
+             << "read storage image";
+    }
   }
 
   if (inst->words().size() <= 5) return SPV_SUCCESS;
 
   const uint32_t mask = inst->word(5);
+
+  if (spvIsOpenCLEnv(_.context()->target_env)) {
+    if (mask & SpvImageOperandsConstOffsetMask) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "ConstOffset image operand not allowed "
+             << "in the OpenCL environment.";
+    }
+  }
+
   if (spv_result_t result =
           ValidateImageOperands(_, inst, info, mask, /* word_index = */ 6))
     return result;
@@ -1405,15 +1527,25 @@
     }
   }
 
-  if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
-      !_.HasCapability(SpvCapabilityStorageImageWriteWithoutFormat)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Capability StorageImageWriteWithoutFormat is required to "
-              "write "
-           << "to storage image";
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
+        !_.HasCapability(SpvCapabilityStorageImageWriteWithoutFormat)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageWriteWithoutFormat is required to "
+                "write "
+             << "to storage image";
+    }
   }
 
-  if (inst->words().size() <= 4) return SPV_SUCCESS;
+  if (inst->words().size() <= 4) {
+    return SPV_SUCCESS;
+  } else {
+    if (spvIsOpenCLEnv(_.context()->target_env)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Optional Image Operands are not allowed in the OpenCL "
+             << "environment.";
+    }
+  }
 
   const uint32_t mask = inst->word(4);
   if (spv_result_t result =
@@ -1578,8 +1710,39 @@
                                    const Instruction* inst) {
   _.function(inst->function()->id())
       ->RegisterExecutionModelLimitation(
-          SpvExecutionModelFragment,
-          "OpImageQueryLod requires Fragment execution model");
+          [&](SpvExecutionModel model, std::string* message) {
+            if (model != SpvExecutionModelFragment &&
+                model != SpvExecutionModelGLCompute) {
+              if (message) {
+                *message = std::string(
+                    "OpImageQueryLod requires Fragment or GLCompute execution "
+                    "model");
+              }
+              return false;
+            }
+            return true;
+          });
+  _.function(inst->function()->id())
+      ->RegisterLimitation([](const ValidationState_t& state,
+                              const Function* entry_point,
+                              std::string* message) {
+        const auto* models = state.GetExecutionModels(entry_point->id());
+        const auto* modes = state.GetExecutionModes(entry_point->id());
+        if (models->find(SpvExecutionModelGLCompute) != models->end() &&
+            modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
+                modes->end() &&
+            modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
+                modes->end()) {
+          if (message) {
+            *message = std::string(
+                "OpImageQueryLod requires DerivativeGroupQuadsNV "
+                "or DerivativeGroupLinearNV execution mode for GLCompute "
+                "execution model");
+          }
+          return false;
+        }
+        return true;
+      });
 
   const uint32_t result_type = inst->type_id();
   if (!_.IsFloatVectorType(result_type)) {
@@ -1703,9 +1866,44 @@
   const SpvOp opcode = inst->opcode();
   if (IsImplicitLod(opcode)) {
     _.function(inst->function()->id())
-        ->RegisterExecutionModelLimitation(
-            SpvExecutionModelFragment,
-            "ImplicitLod instructions require Fragment execution model");
+        ->RegisterExecutionModelLimitation([opcode](SpvExecutionModel model,
+                                                    std::string* message) {
+          if (model != SpvExecutionModelFragment &&
+              model != SpvExecutionModelGLCompute) {
+            if (message) {
+              *message =
+                  std::string(
+                      "ImplicitLod instructions require Fragment or GLCompute "
+                      "execution model: ") +
+                  spvOpcodeString(opcode);
+            }
+            return false;
+          }
+          return true;
+        });
+    _.function(inst->function()->id())
+        ->RegisterLimitation([opcode](const ValidationState_t& state,
+                                      const Function* entry_point,
+                                      std::string* message) {
+          const auto* models = state.GetExecutionModels(entry_point->id());
+          const auto* modes = state.GetExecutionModes(entry_point->id());
+          if (models->find(SpvExecutionModelGLCompute) != models->end() &&
+              modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
+                  modes->end() &&
+              modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
+                  modes->end()) {
+            if (message) {
+              *message =
+                  std::string(
+                      "ImplicitLod instructions require DerivativeGroupQuadsNV "
+                      "or DerivativeGroupLinearNV execution mode for GLCompute "
+                      "execution model: ") +
+                  spvOpcodeString(opcode);
+            }
+            return false;
+          }
+          return true;
+        });
   }
 
   switch (opcode) {
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index 17949d2..b74b535 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -14,10 +14,9 @@
 
 // Performs validation on instructions that appear inside of a SPIR-V block.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <cassert>
+#include <iomanip>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -35,6 +34,7 @@
 #include "source/spirv_validator_options.h"
 #include "source/util/string_utils.h"
 #include "source/val/function.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -55,6 +55,18 @@
   return ss.str();
 }
 
+bool IsValidWebGPUStorageClass(SpvStorageClass storage_class) {
+  return storage_class == SpvStorageClassUniformConstant ||
+         storage_class == SpvStorageClassUniform ||
+         storage_class == SpvStorageClassStorageBuffer ||
+         storage_class == SpvStorageClassInput ||
+         storage_class == SpvStorageClassOutput ||
+         storage_class == SpvStorageClassImage ||
+         storage_class == SpvStorageClassWorkgroup ||
+         storage_class == SpvStorageClassPrivate ||
+         storage_class == SpvStorageClassFunction;
+}
+
 // Returns capabilities that enable an opcode.  An empty result is interpreted
 // as no prohibition of use of the opcode.  If the result is non-empty, then
 // the opcode may only be used if at least one of the capabilities is specified
@@ -86,21 +98,72 @@
   return CapabilitySet();
 }
 
+// Returns SPV_SUCCESS if, for the given operand, the target environment
+// satsifies minimum version requirements, or if the module declares an
+// enabling extension for the operand.  Otherwise emit a diagnostic and
+// return an error code.
+spv_result_t OperandVersionExtensionCheck(
+    ValidationState_t& _, const Instruction* inst, size_t which_operand,
+    const spv_operand_desc_t& operand_desc, uint32_t word) {
+  const uint32_t module_version = _.version();
+  const uint32_t operand_min_version = operand_desc.minVersion;
+  const uint32_t operand_last_version = operand_desc.lastVersion;
+  const bool reserved = operand_min_version == 0xffffffffu;
+  const bool version_satisfied = !reserved &&
+                                 (operand_min_version <= module_version) &&
+                                 (module_version <= operand_last_version);
+
+  if (version_satisfied) {
+    return SPV_SUCCESS;
+  }
+
+  if (operand_last_version < module_version) {
+    return _.diag(SPV_ERROR_WRONG_VERSION, inst)
+           << spvtools::utils::CardinalToOrdinal(which_operand)
+           << " operand of " << spvOpcodeString(inst->opcode()) << ": operand "
+           << operand_desc.name << "(" << word << ") requires SPIR-V version "
+           << SPV_SPIRV_VERSION_MAJOR_PART(operand_last_version) << "."
+           << SPV_SPIRV_VERSION_MINOR_PART(operand_last_version)
+           << " or earlier";
+  }
+
+  if (!reserved && operand_desc.numExtensions == 0) {
+    return _.diag(SPV_ERROR_WRONG_VERSION, inst)
+           << spvtools::utils::CardinalToOrdinal(which_operand)
+           << " operand of " << spvOpcodeString(inst->opcode()) << ": operand "
+           << operand_desc.name << "(" << word << ") requires SPIR-V version "
+           << SPV_SPIRV_VERSION_MAJOR_PART(operand_min_version) << "."
+           << SPV_SPIRV_VERSION_MINOR_PART(operand_min_version) << " or later";
+  } else {
+    ExtensionSet required_extensions(operand_desc.numExtensions,
+                                     operand_desc.extensions);
+    if (!_.HasAnyOfExtensions(required_extensions)) {
+      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
+             << spvtools::utils::CardinalToOrdinal(which_operand)
+             << " operand of " << spvOpcodeString(inst->opcode())
+             << ": operand " << operand_desc.name << "(" << word
+             << ") requires one of these extensions: "
+             << ExtensionSetToString(required_extensions);
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 // Returns SPV_SUCCESS if the given operand is enabled by capabilities declared
 // in the module.  Otherwise issues an error message and returns
 // SPV_ERROR_INVALID_CAPABILITY.
 spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
                                        const Instruction* inst,
                                        size_t which_operand,
-                                       spv_operand_type_t type,
-                                       uint32_t operand) {
+                                       const spv_parsed_operand_t& operand,
+                                       uint32_t word) {
   // Mere mention of PointSize, ClipDistance, or CullDistance in a Builtin
   // decoration does not require the associated capability.  The use of such
   // a variable value should trigger the capability requirement, but that's
   // not implemented yet.  This rule is independent of target environment.
   // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
-  if (type == SPV_OPERAND_TYPE_BUILT_IN) {
-    switch (operand) {
+  if (operand.type == SPV_OPERAND_TYPE_BUILT_IN) {
+    switch (word) {
       case SpvBuiltInPointSize:
       case SpvBuiltInClipDistance:
       case SpvBuiltInCullDistance:
@@ -108,14 +171,14 @@
       default:
         break;
     }
-  } else if (type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
+  } else if (operand.type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
     // Allow all FP rounding modes if requested
     if (state.features().free_fp_rounding_mode) {
       return SPV_SUCCESS;
     }
-  } else if (type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
+  } else if (operand.type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
              state.features().group_ops_reduce_and_scans &&
-             (operand <= uint32_t(SpvGroupOperationExclusiveScan))) {
+             (word <= uint32_t(SpvGroupOperationExclusiveScan))) {
     // Allow certain group operations if requested.
     return SPV_SUCCESS;
   }
@@ -123,10 +186,10 @@
   CapabilitySet enabling_capabilities;
   spv_operand_desc operand_desc = nullptr;
   const auto lookup_result =
-      state.grammar().lookupOperand(type, operand, &operand_desc);
+      state.grammar().lookupOperand(operand.type, word, &operand_desc);
   if (lookup_result == SPV_SUCCESS) {
     // Allow FPRoundingMode decoration if requested.
-    if (type == SPV_OPERAND_TYPE_DECORATION &&
+    if (operand.type == SPV_OPERAND_TYPE_DECORATION &&
         operand_desc->value == SpvDecorationFPRoundingMode) {
       if (state.features().free_fp_rounding_mode) return SPV_SUCCESS;
 
@@ -149,29 +212,13 @@
              << " requires one of these capabilities: "
              << ToString(enabling_capabilities, state.grammar());
     }
+    return OperandVersionExtensionCheck(state, inst, which_operand,
+                                        *operand_desc, word);
   }
 
   return SPV_SUCCESS;
 }
 
-// Returns operand's required extensions.
-ExtensionSet RequiredExtensions(const ValidationState_t& state,
-                                spv_operand_type_t type, uint32_t operand) {
-  spv_operand_desc operand_desc;
-  if (state.grammar().lookupOperand(type, operand, &operand_desc) ==
-      SPV_SUCCESS) {
-    assert(operand_desc);
-    // If this operand is incorporated into core SPIR-V before or in the current
-    // target environment, we don't require extensions anymore.
-    if (spvVersionForTargetEnv(state.grammar().target_env()) >=
-        operand_desc->minVersion)
-      return {};
-    return {operand_desc->numExtensions, operand_desc->extensions};
-  }
-
-  return {};
-}
-
 // Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction
 // is explicitly reserved in the SPIR-V core spec.  Otherwise return
 // SPV_SUCCESS.
@@ -232,7 +279,7 @@
       for (uint32_t mask_bit = 0x80000000; mask_bit; mask_bit >>= 1) {
         if (word & mask_bit) {
           spv_result_t status =
-              CheckRequiredCapabilities(_, inst, i + 1, operand.type, mask_bit);
+              CheckRequiredCapabilities(_, inst, i + 1, operand, mask_bit);
           if (status != SPV_SUCCESS) return status;
         }
       }
@@ -243,34 +290,13 @@
     } else {
       // Check the operand word as a whole.
       spv_result_t status =
-          CheckRequiredCapabilities(_, inst, i + 1, operand.type, word);
+          CheckRequiredCapabilities(_, inst, i + 1, operand, word);
       if (status != SPV_SUCCESS) return status;
     }
   }
   return SPV_SUCCESS;
 }
 
-// Checks that all extensions required by the given instruction's operands were
-// declared in the module.
-spv_result_t ExtensionCheck(ValidationState_t& _, const Instruction* inst) {
-  const SpvOp opcode = inst->opcode();
-  for (size_t operand_index = 0; operand_index < inst->operands().size();
-       ++operand_index) {
-    const auto& operand = inst->operand(operand_index);
-    const uint32_t word = inst->word(operand.offset);
-    const ExtensionSet required_extensions =
-        RequiredExtensions(_, operand.type, word);
-    if (!_.HasAnyOfExtensions(required_extensions)) {
-      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
-             << spvtools::utils::CardinalToOrdinal(operand_index + 1)
-             << " operand of " << spvOpcodeString(opcode) << ": operand "
-             << word << " requires one of these extensions: "
-             << ExtensionSetToString(required_extensions);
-    }
-  }
-  return SPV_SUCCESS;
-}
-
 // Checks that the instruction can be used in this target environment's base
 // version. Assumes that CapabilityCheck has checked direct capability
 // dependencies for the opcode.
@@ -282,6 +308,15 @@
   (void)r;
 
   const auto min_version = inst_desc->minVersion;
+  const auto last_version = inst_desc->lastVersion;
+  const auto module_version = _.version();
+
+  if (last_version < module_version) {
+    return _.diag(SPV_ERROR_WRONG_VERSION, inst)
+           << spvOpcodeString(opcode) << " requires SPIR-V version "
+           << SPV_SPIRV_VERSION_MAJOR_PART(last_version) << "."
+           << SPV_SPIRV_VERSION_MINOR_PART(last_version) << " or earlier";
+  }
 
   if (inst_desc->numCapabilities > 0u) {
     // We already checked that the direct capability dependency has been
@@ -291,22 +326,23 @@
 
   ExtensionSet exts(inst_desc->numExtensions, inst_desc->extensions);
   if (exts.IsEmpty()) {
-    // If no extensions can enable this instruction, then emit error messages
-    // only concerning core SPIR-V versions if errors happen.
+    // If no extensions can enable this instruction, then emit error
+    // messages only concerning core SPIR-V versions if errors happen.
     if (min_version == ~0u) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
              << spvOpcodeString(opcode) << " is reserved for future use.";
     }
 
-    if (spvVersionForTargetEnv(_.grammar().target_env()) < min_version) {
+    if (module_version < min_version) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
              << spvOpcodeString(opcode) << " requires "
              << spvTargetEnvDescription(
                     static_cast<spv_target_env>(min_version))
              << " at minimum.";
     }
-  // Otherwise, we only error out when no enabling extensions are registered.
   } else if (!_.HasAnyOfExtensions(exts)) {
+    // Otherwise, we only error out when no enabling extensions are
+    // registered.
     if (min_version == ~0u) {
       return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
              << spvOpcodeString(opcode)
@@ -314,11 +350,11 @@
              << ExtensionSetToString(exts);
     }
 
-    if (static_cast<uint32_t>(_.grammar().target_env()) < min_version) {
+    if (module_version < min_version) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
-             << spvOpcodeString(opcode) << " requires "
-             << spvTargetEnvDescription(
-                    static_cast<spv_target_env>(min_version))
+             << spvOpcodeString(opcode) << " requires SPIR-V version "
+             << SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "."
+             << SPV_SPIRV_VERSION_MINOR_PART(min_version)
              << " at minimum or one of the following extensions: "
              << ExtensionSetToString(exts);
     }
@@ -355,11 +391,10 @@
 
   // Section 2.17 of SPIRV Spec specifies that the "Structure Nesting Depth"
   // must be less than or equal to 255.
-  // This is interpreted as structures including other structures as members.
-  // The code does not follow pointers or look into arrays to see if we reach a
-  // structure downstream.
-  // The nesting depth of a struct is 1+(largest depth of any member).
-  // Scalars are at depth 0.
+  // This is interpreted as structures including other structures as
+  // members. The code does not follow pointers or look into arrays to see
+  // if we reach a structure downstream. The nesting depth of a struct is
+  // 1+(largest depth of any member). Scalars are at depth 0.
   uint32_t max_member_depth = 0;
   // Struct members start at word 2 of OpTypeStruct instruction.
   for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) {
@@ -382,8 +417,8 @@
   return SPV_SUCCESS;
 }
 
-// Checks that the number of (literal, label) pairs in OpSwitch is within the
-// limit.
+// Checks that the number of (literal, label) pairs in OpSwitch is within
+// the limit.
 spv_result_t LimitCheckSwitch(ValidationState_t& _, const Instruction* inst) {
   if (SpvOpSwitch == inst->opcode()) {
     // The instruction syntax is as follows:
@@ -402,7 +437,8 @@
   return SPV_SUCCESS;
 }
 
-// Ensure the number of variables of the given class does not exceed the limit.
+// Ensure the number of variables of the given class does not exceed the
+// limit.
 spv_result_t LimitCheckNumVars(ValidationState_t& _, const uint32_t var_id,
                                const SpvStorageClass storage_class) {
   if (SpvStorageClassFunction == storage_class) {
@@ -460,7 +496,8 @@
     if (_.memory_model() != SpvMemoryModelVulkanKHR &&
         _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "VulkanMemoryModelKHR capability must only be specified if the "
+             << "VulkanMemoryModelKHR capability must only be specified if "
+                "the "
                 "VulkanKHR memory model is used.";
     }
 
@@ -474,6 +511,19 @@
                << "Memory model must be VulkanKHR for WebGPU environment.";
       }
     }
+
+    if (spvIsOpenCLEnv(_.context()->target_env)) {
+      if ((_.addressing_model() != SpvAddressingModelPhysical32) &&
+          (_.addressing_model() != SpvAddressingModelPhysical64)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Addressing model must be Physical32 or Physical64 "
+               << "in the OpenCL environment.";
+      }
+      if (_.memory_model() != SpvMemoryModelOpenCL) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Memory model must be OpenCL in the OpenCL environment.";
+      }
+    }
   } else if (opcode == SpvOpExecutionMode) {
     const uint32_t entry_point = inst->word(1);
     _.RegisterExecutionModeForEntryPoint(entry_point,
@@ -483,6 +533,15 @@
     if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) {
       return error;
     }
+
+    if (spvIsWebGPUEnv(_.context()->target_env) &&
+        !IsValidWebGPUStorageClass(storage_class)) {
+      return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+             << "For WebGPU, OpVariable storage class must be one of "
+                "UniformConstant, Uniform, StorageBuffer, Input, Output, "
+                "Image, Workgroup, Private, Function for WebGPU";
+    }
+
     if (storage_class == SpvStorageClassGeneric)
       return _.diag(SPV_ERROR_INVALID_BINARY, inst)
              << "OpVariable storage class cannot be Generic";
@@ -506,6 +565,15 @@
                   "outside of a function";
       }
     }
+  } else if (opcode == SpvOpTypePointer) {
+    const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
+    if (spvIsWebGPUEnv(_.context()->target_env) &&
+        !IsValidWebGPUStorageClass(storage_class)) {
+      return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+             << "For WebGPU, OpTypePointer storage class must be one of "
+                "UniformConstant, Uniform, StorageBuffer, Input, Output, "
+                "Image, Workgroup, Private, Function";
+    }
   }
 
   // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The
@@ -518,7 +586,6 @@
               "capability is used.";
   }
 
-  if (auto error = ExtensionCheck(_, inst)) return error;
   if (auto error = ReservedCheck(_, inst)) return error;
   if (auto error = EnvironmentCheck(_, inst)) return error;
   if (auto error = CapabilityCheck(_, inst)) return error;
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index fffc6da..c85b673 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <vector>
 
 #include "source/diagnostic.h"
+#include "source/spirv_constant.h"
+#include "source/spirv_target_env.h"
 #include "source/val/function.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -27,10 +28,16 @@
 namespace {
 
 // Returns true if \c inst is an input or output variable.
-bool is_interface_variable(const Instruction* inst) {
-  return inst->opcode() == SpvOpVariable &&
-         (inst->word(3u) == SpvStorageClassInput ||
-          inst->word(3u) == SpvStorageClassOutput);
+bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
+  if (is_spv_1_4) {
+    // Starting in SPIR-V 1.4, all global variables are interface variables.
+    return inst->opcode() == SpvOpVariable &&
+           inst->word(3u) != SpvStorageClassFunction;
+  } else {
+    return inst->opcode() == SpvOpVariable &&
+           (inst->word(3u) == SpvStorageClassInput ||
+            inst->word(3u) == SpvStorageClassOutput);
+  }
 }
 
 // Checks that \c var is listed as an interface in all the entry points that use
@@ -85,9 +92,8 @@
       }
       if (!found) {
         return _.diag(SPV_ERROR_INVALID_ID, var)
-               << (var->word(3u) == SpvStorageClassInput ? "Input" : "Output")
-               << " variable id <" << var->id() << "> is used by entry point '"
-               << desc.name << "' id <" << id
+               << "Interface variable id <" << var->id()
+               << "> is used by entry point '" << desc.name << "' id <" << id
                << ">, but is not listed as an interface";
       }
     }
@@ -99,8 +105,9 @@
 }  // namespace
 
 spv_result_t ValidateInterfaces(ValidationState_t& _) {
+  bool is_spv_1_4 = _.version() >= SPV_SPIRV_VERSION_WORD(1, 4);
   for (auto& inst : _.ordered_instructions()) {
-    if (is_interface_variable(&inst)) {
+    if (is_interface_variable(&inst, is_spv_1_4)) {
       if (auto error = check_interface_variable(_, &inst)) {
         return error;
       }
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp
index a25460b..5886dbf 100644
--- a/source/val/validate_logicals.cpp
+++ b/source/val/validate_logicals.cpp
@@ -151,6 +151,14 @@
         const Instruction* type_inst = _.FindDef(result_type);
         assert(type_inst);
 
+        const auto composites = _.features().select_between_composites;
+        auto fail = [&_, composites, inst, opcode]() -> spv_result_t {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected scalar or "
+                 << (composites ? "composite" : "vector")
+                 << " type as Result Type: " << spvOpcodeString(opcode);
+        };
+
         const SpvOp type_opcode = type_inst->opcode();
         switch (type_opcode) {
           case SpvOpTypePointer: {
@@ -174,35 +182,48 @@
             break;
           }
 
-          default: {
+          // Not RuntimeArray because of other rules.
+          case SpvOpTypeArray:
+          case SpvOpTypeMatrix:
+          case SpvOpTypeStruct: {
+            if (!composites) return fail();
+            break;
+          };
+
+          default:
+            return fail();
+        }
+
+        const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
+        const uint32_t left_type = _.GetOperandTypeId(inst, 3);
+        const uint32_t right_type = _.GetOperandTypeId(inst, 4);
+
+        if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
+                                !_.IsBoolVectorType(condition_type)))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected bool scalar or vector type as condition: "
+                 << spvOpcodeString(opcode);
+
+        if (_.GetDimension(condition_type) != dimension) {
+          // If the condition is a vector type, then the result must also be a
+          // vector with matching dimensions. In SPIR-V 1.4, a scalar condition
+          // can be used to select between vector types. |composites| is a
+          // proxy for SPIR-V 1.4 functionality.
+          if (!composites || _.IsBoolVectorType(condition_type)) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << "Expected scalar or vector type as Result Type: "
+                   << "Expected vector sizes of Result Type and the condition "
+                      "to be equal: "
                    << spvOpcodeString(opcode);
           }
         }
+
+        if (result_type != left_type || result_type != right_type)
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected both objects to be of Result Type: "
+                 << spvOpcodeString(opcode);
+
+        break;
       }
-
-      const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
-      const uint32_t left_type = _.GetOperandTypeId(inst, 3);
-      const uint32_t right_type = _.GetOperandTypeId(inst, 4);
-
-      if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
-                              !_.IsBoolVectorType(condition_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected bool scalar or vector type as condition: "
-               << spvOpcodeString(opcode);
-
-      if (_.GetDimension(condition_type) != dimension)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected vector sizes of Result Type and the condition to be"
-               << " equal: " << spvOpcodeString(opcode);
-
-      if (result_type != left_type || result_type != right_type)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected both objects to be of Result Type: "
-               << spvOpcodeString(opcode);
-
-      break;
     }
 
     case SpvOpIEqual:
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 3b104be..f31c5a0 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <string>
 #include <vector>
@@ -21,6 +19,7 @@
 #include "source/opcode.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
@@ -197,17 +196,49 @@
   return false;
 }
 
+bool ContainsCooperativeMatrix(ValidationState_t& _,
+                               const Instruction* storage) {
+  const size_t elem_type_index = 1;
+  uint32_t elem_type_id;
+  Instruction* elem_type;
+
+  switch (storage->opcode()) {
+    case SpvOpTypeCooperativeMatrixNV:
+      return true;
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      elem_type_id = storage->GetOperandAs<uint32_t>(elem_type_index);
+      elem_type = _.FindDef(elem_type_id);
+      return ContainsCooperativeMatrix(_, elem_type);
+    case SpvOpTypeStruct:
+      for (size_t member_type_index = 1;
+           member_type_index < storage->operands().size();
+           ++member_type_index) {
+        auto member_type_id =
+            storage->GetOperandAs<uint32_t>(member_type_index);
+        auto member_type = _.FindDef(member_type_id);
+        if (ContainsCooperativeMatrix(_, member_type)) return true;
+      }
+      break;
+    default:
+      break;
+  }
+  return false;
+}
+
 std::pair<SpvStorageClass, SpvStorageClass> GetStorageClass(
     ValidationState_t& _, const Instruction* inst) {
   SpvStorageClass dst_sc = SpvStorageClassMax;
   SpvStorageClass src_sc = SpvStorageClassMax;
   switch (inst->opcode()) {
+    case SpvOpCooperativeMatrixLoadNV:
     case SpvOpLoad: {
       auto load_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(2));
       auto load_pointer_type = _.FindDef(load_pointer->type_id());
       dst_sc = load_pointer_type->GetOperandAs<SpvStorageClass>(1);
       break;
     }
+    case SpvOpCooperativeMatrixStoreNV:
     case SpvOpStore: {
       auto store_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(0));
       auto store_pointer_type = _.FindDef(store_pointer->type_id());
@@ -231,49 +262,40 @@
   return std::make_pair(dst_sc, src_sc);
 }
 
-// This function is only called for OpLoad, OpStore, OpCopyMemory and
-// OpCopyMemorySized.
-uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask) {
-  uint32_t offset = 1;
-  if (mask & SpvMemoryAccessAlignedMask) ++offset;
-
-  uint32_t scope_id = 0;
-  switch (inst->opcode()) {
-    case SpvOpLoad:
-    case SpvOpCopyMemorySized:
-      return inst->GetOperandAs<uint32_t>(3 + offset);
-    case SpvOpStore:
-    case SpvOpCopyMemory:
-      return inst->GetOperandAs<uint32_t>(2 + offset);
-    default:
-      assert(false && "unexpected opcode");
-      break;
-  }
-
-  return scope_id;
+// Returns the number of instruction words taken up by a memory access
+// argument and its implied operands.
+int MemoryAccessNumWords(uint32_t mask) {
+  int result = 1;  // Count the mask
+  if (mask & SpvMemoryAccessAlignedMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
+  return result;
 }
 
+// Returns the scope ID operand for MakeAvailable memory access with mask
+// at the given operand index.
 // This function is only called for OpLoad, OpStore, OpCopyMemory and
-// OpCopyMemorySized.
-uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
-  uint32_t offset = 1;
-  if (mask & SpvMemoryAccessAlignedMask) ++offset;
-  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++offset;
+// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
+// OpCooperativeMatrixStoreNV.
+uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask,
+                               uint32_t mask_index) {
+  assert(mask & SpvMemoryAccessMakePointerAvailableKHRMask);
+  uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerAvailableKHRMask);
+  uint32_t index =
+      mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
+  return inst->GetOperandAs<uint32_t>(index);
+}
 
-  uint32_t scope_id = 0;
-  switch (inst->opcode()) {
-    case SpvOpLoad:
-    case SpvOpCopyMemorySized:
-      return inst->GetOperandAs<uint32_t>(3 + offset);
-    case SpvOpStore:
-    case SpvOpCopyMemory:
-      return inst->GetOperandAs<uint32_t>(2 + offset);
-    default:
-      assert(false && "unexpected opcode");
-      break;
-  }
-
-  return scope_id;
+// This function is only called for OpLoad, OpStore, OpCopyMemory,
+// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
+// OpCooperativeMatrixStoreNV.
+uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask,
+                             uint32_t mask_index) {
+  assert(mask & SpvMemoryAccessMakePointerVisibleKHRMask);
+  uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerVisibleKHRMask);
+  uint32_t index =
+      mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
+  return inst->GetOperandAs<uint32_t>(index);
 }
 
 bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) {
@@ -300,9 +322,10 @@
     return SPV_SUCCESS;
   }
 
-  uint32_t mask = inst->GetOperandAs<uint32_t>(index);
+  const uint32_t mask = inst->GetOperandAs<uint32_t>(index);
   if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
-    if (inst->opcode() == SpvOpLoad) {
+    if (inst->opcode() == SpvOpLoad ||
+        inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "MakePointerAvailableKHR cannot be used with OpLoad.";
     }
@@ -314,13 +337,14 @@
     }
 
     // Check the associated scope for MakeAvailableKHR.
-    const auto available_scope = GetMakeAvailableScope(inst, mask);
+    const auto available_scope = GetMakeAvailableScope(inst, mask, index);
     if (auto error = ValidateMemoryScope(_, inst, available_scope))
       return error;
   }
 
   if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) {
-    if (inst->opcode() == SpvOpStore) {
+    if (inst->opcode() == SpvOpStore ||
+        inst->opcode() == SpvOpCooperativeMatrixStoreNV) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "MakePointerVisibleKHR cannot be used with OpStore.";
     }
@@ -332,7 +356,7 @@
     }
 
     // Check the associated scope for MakeVisibleKHR.
-    const auto visible_scope = GetMakeVisibleScope(inst, mask);
+    const auto visible_scope = GetMakeVisibleScope(inst, mask, index);
     if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
@@ -397,10 +421,14 @@
              << "OpVariable Initializer <id> '" << _.getIdName(initializer_id)
              << "' is not a constant or module-scope variable.";
     }
+    if (initializer->type_id() != result_type->GetOperandAs<uint32_t>(2u)) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Initializer type must match the type pointed to by the Result "
+                "Type";
+    }
   }
 
-  const auto storage_class =
-      inst->GetOperandAs<SpvStorageClass>(storage_class_index);
+  auto storage_class = inst->GetOperandAs<SpvStorageClass>(storage_class_index);
   if (storage_class != SpvStorageClassWorkgroup &&
       storage_class != SpvStorageClassCrossWorkgroup &&
       storage_class != SpvStorageClassPrivate &&
@@ -515,23 +543,13 @@
   if (inst->operands().size() > 3 && storage_class != SpvStorageClassOutput &&
       storage_class != SpvStorageClassPrivate &&
       storage_class != SpvStorageClassFunction) {
-    if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpVariable, <id> '" << _.getIdName(inst->id())
              << "', has a disallowed initializer & storage class "
              << "combination.\n"
-             << "From Vulkan spec, Appendix A:\n"
-             << "Variable declarations that include initializers must have "
-             << "one of the following storage classes: Output, Private, or "
-             << "Function";
-    }
-
-    if (spvIsWebGPUEnv(_.context()->target_env)) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpVariable, <id> '" << _.getIdName(inst->id())
-             << "', has a disallowed initializer & storage class "
-             << "combination.\n"
-             << "From WebGPU execution environment spec:\n"
+             << "From " << spvLogStringForEnv(_.context()->target_env)
+             << " spec:\n"
              << "Variable declarations that include initializers must have "
              << "one of the following storage classes: Output, Private, or "
              << "Function";
@@ -585,10 +603,10 @@
   }
 
   // Vulkan specific validation rules for OpTypeRuntimeArray
+  const auto type_index = 2;
+  const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
+  auto value_type = _.FindDef(value_id);
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    const auto type_index = 2;
-    const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
-    auto value_type = _.FindDef(value_id);
     // OpTypeRuntimeArray should only ever be in a container like OpTypeStruct,
     // so should never appear as a bare variable.
     // Unless the module has the RuntimeDescriptorArrayEXT capability.
@@ -646,9 +664,6 @@
 
   // WebGPU specific validation rules for OpTypeRuntimeArray
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    const auto type_index = 2;
-    const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
-    auto value_type = _.FindDef(value_id);
     // OpTypeRuntimeArray should only ever be in an OpTypeStruct,
     // so should never appear as a bare variable.
     if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
@@ -682,6 +697,130 @@
     }
   }
 
+  // Cooperative matrix types can only be allocated in Function or Private
+  if ((storage_class != SpvStorageClassFunction &&
+       storage_class != SpvStorageClassPrivate) &&
+      ContainsCooperativeMatrix(_, pointee)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cooperative matrix types (or types containing them) can only be "
+              "allocated "
+           << "in Function or Private storage classes or as function "
+              "parameters";
+  }
+
+  if (_.HasCapability(SpvCapabilityShader)) {
+    // Don't allow variables containing 16-bit elements without the appropriate
+    // capabilities.
+    if ((!_.HasCapability(SpvCapabilityInt16) &&
+         _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 16)) ||
+        (!_.HasCapability(SpvCapabilityFloat16) &&
+         _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeFloat, 16))) {
+      auto underlying_type = value_type;
+      while (underlying_type->opcode() == SpvOpTypePointer) {
+        storage_class = underlying_type->GetOperandAs<SpvStorageClass>(1u);
+        underlying_type =
+            _.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
+      }
+      bool storage_class_ok = true;
+      std::string sc_name = _.grammar().lookupOperandName(
+          SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
+      switch (storage_class) {
+        case SpvStorageClassStorageBuffer:
+        case SpvStorageClassPhysicalStorageBufferEXT:
+          if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess)) {
+            storage_class_ok = false;
+          }
+          break;
+        case SpvStorageClassUniform:
+          if (!_.HasCapability(
+                  SpvCapabilityUniformAndStorageBuffer16BitAccess)) {
+            if (underlying_type->opcode() == SpvOpTypeArray ||
+                underlying_type->opcode() == SpvOpTypeRuntimeArray) {
+              underlying_type =
+                  _.FindDef(underlying_type->GetOperandAs<uint32_t>(1u));
+            }
+            if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess) ||
+                !_.HasDecoration(underlying_type->id(),
+                                 SpvDecorationBufferBlock)) {
+              storage_class_ok = false;
+            }
+          }
+          break;
+        case SpvStorageClassPushConstant:
+          if (!_.HasCapability(SpvCapabilityStoragePushConstant16)) {
+            storage_class_ok = false;
+          }
+          break;
+        case SpvStorageClassInput:
+        case SpvStorageClassOutput:
+          if (!_.HasCapability(SpvCapabilityStorageInputOutput16)) {
+            storage_class_ok = false;
+          }
+          break;
+        default:
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "Cannot allocate a variable containing a 16-bit type in "
+                 << sc_name << " storage class";
+      }
+      if (!storage_class_ok) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Allocating a variable containing a 16-bit element in "
+               << sc_name << " storage class requires an additional capability";
+      }
+    }
+    // Don't allow variables containing 8-bit elements without the appropriate
+    // capabilities.
+    if (!_.HasCapability(SpvCapabilityInt8) &&
+        _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 8)) {
+      auto underlying_type = value_type;
+      while (underlying_type->opcode() == SpvOpTypePointer) {
+        storage_class = underlying_type->GetOperandAs<SpvStorageClass>(1u);
+        underlying_type =
+            _.FindDef(underlying_type->GetOperandAs<uint32_t>(2u));
+      }
+      bool storage_class_ok = true;
+      std::string sc_name = _.grammar().lookupOperandName(
+          SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
+      switch (storage_class) {
+        case SpvStorageClassStorageBuffer:
+        case SpvStorageClassPhysicalStorageBufferEXT:
+          if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess)) {
+            storage_class_ok = false;
+          }
+          break;
+        case SpvStorageClassUniform:
+          if (!_.HasCapability(
+                  SpvCapabilityUniformAndStorageBuffer8BitAccess)) {
+            if (underlying_type->opcode() == SpvOpTypeArray ||
+                underlying_type->opcode() == SpvOpTypeRuntimeArray) {
+              underlying_type =
+                  _.FindDef(underlying_type->GetOperandAs<uint32_t>(1u));
+            }
+            if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess) ||
+                !_.HasDecoration(underlying_type->id(),
+                                 SpvDecorationBufferBlock)) {
+              storage_class_ok = false;
+            }
+          }
+          break;
+        case SpvStorageClassPushConstant:
+          if (!_.HasCapability(SpvCapabilityStoragePushConstant8)) {
+            storage_class_ok = false;
+          }
+          break;
+        default:
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "Cannot allocate a variable containing a 8-bit type in "
+                 << sc_name << " storage class";
+      }
+      if (!storage_class_ok) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Allocating a variable containing a 8-bit element in "
+               << sc_name << " storage class requires an additional capability";
+      }
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -727,6 +866,18 @@
 
   if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
 
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id()) &&
+      result_type->opcode() != SpvOpTypePointer) {
+    if (result_type->opcode() != SpvOpTypeInt &&
+        result_type->opcode() != SpvOpTypeFloat &&
+        result_type->opcode() != SpvOpTypeVector &&
+        result_type->opcode() != SpvOpTypeMatrix) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "8- or 16-bit loads must be a scalar, vector or matrix type";
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -778,6 +929,25 @@
              << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
              << "' storage class is read-only";
     }
+
+    if (spvIsVulkanEnv(_.context()->target_env) &&
+        storage_class == SpvStorageClassUniform) {
+      auto base_ptr = _.TracePointer(pointer);
+      if (base_ptr->opcode() == SpvOpVariable) {
+        // If it's not a variable a different check should catch the problem.
+        auto base_type = _.FindDef(base_ptr->GetOperandAs<uint32_t>(0));
+        // Get the pointed-to type.
+        base_type = _.FindDef(base_type->GetOperandAs<uint32_t>(2u));
+        if (base_type->opcode() == SpvOpTypeArray ||
+            base_type->opcode() == SpvOpTypeRuntimeArray) {
+          base_type = _.FindDef(base_type->GetOperandAs<uint32_t>(1u));
+        }
+        if (_.HasDecoration(base_type->id(), SpvDecorationBlock)) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "In the Vulkan environment, cannot store to Uniform Blocks";
+        }
+      }
+    }
   }
 
   const auto object_index = 1;
@@ -815,6 +985,63 @@
 
   if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
 
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id()) &&
+      object_type->opcode() != SpvOpTypePointer) {
+    if (object_type->opcode() != SpvOpTypeInt &&
+        object_type->opcode() != SpvOpTypeFloat &&
+        object_type->opcode() != SpvOpTypeVector &&
+        object_type->opcode() != SpvOpTypeMatrix) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "8- or 16-bit stores must be a scalar, vector or matrix type";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateCopyMemoryMemoryAccess(ValidationState_t& _,
+                                            const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCopyMemory ||
+         inst->opcode() == SpvOpCopyMemorySized);
+  const uint32_t first_access_index = inst->opcode() == SpvOpCopyMemory ? 2 : 3;
+  if (inst->operands().size() > first_access_index) {
+    if (auto error = CheckMemoryAccess(_, inst, first_access_index))
+      return error;
+
+    const auto first_access = inst->GetOperandAs<uint32_t>(first_access_index);
+    const uint32_t second_access_index =
+        first_access_index + MemoryAccessNumWords(first_access);
+    if (inst->operands().size() > second_access_index) {
+      if (_.features().copy_memory_permits_two_memory_accesses) {
+        if (auto error = CheckMemoryAccess(_, inst, second_access_index))
+          return error;
+
+        // In the two-access form in SPIR-V 1.4 and later:
+        //  - the first is the target (write) access and it can't have
+        //  make-visible.
+        //  - the second is the source (read) access and it can't have
+        //  make-available.
+        if (first_access & SpvMemoryAccessMakePointerVisibleKHRMask) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Target memory access must not include "
+                    "MakePointerVisibleKHR";
+        }
+        const auto second_access =
+            inst->GetOperandAs<uint32_t>(second_access_index);
+        if (second_access & SpvMemoryAccessMakePointerAvailableKHRMask) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Source memory access must not include "
+                    "MakePointerAvailableKHR";
+        }
+      } else {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(static_cast<SpvOp>(inst->opcode()))
+               << " with two memory access operands requires SPIR-V 1.4 or "
+                  "later";
+      }
+    }
+  }
   return SPV_SUCCESS;
 }
 
@@ -923,6 +1150,19 @@
 
     if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
   }
+  if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error;
+
+  // Get past the pointers to avoid checking a pointer copy.
+  auto sub_type = _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
+  while (sub_type->opcode() == SpvOpTypePointer) {
+    sub_type = _.FindDef(sub_type->GetOperandAs<uint32_t>(2));
+  }
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(sub_type->id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cannot copy memory of objects containing 8- or 16-bit types";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -1013,10 +1253,11 @@
     switch (type_pointee->opcode()) {
       case SpvOpTypeMatrix:
       case SpvOpTypeVector:
+      case SpvOpTypeCooperativeMatrixNV:
       case SpvOpTypeArray:
       case SpvOpTypeRuntimeArray: {
-        // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray,
-        // word 2 is the Element Type.
+        // In OpTypeMatrix, OpTypeVector, SpvOpTypeCooperativeMatrixNV,
+        // OpTypeArray, and OpTypeRuntimeArray, word 2 is the Element Type.
         type_pointee = _.FindDef(type_pointee->word(2));
         break;
       }
@@ -1146,6 +1387,191 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateCooperativeMatrixLengthNV(ValidationState_t& state,
+                                               const Instruction* inst) {
+  std::string instr_name =
+      "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode())));
+
+  // Result type must be a 32-bit unsigned int.
+  auto result_type = state.FindDef(inst->type_id());
+  if (result_type->opcode() != SpvOpTypeInt ||
+      result_type->GetOperandAs<uint32_t>(1) != 32 ||
+      result_type->GetOperandAs<uint32_t>(2) != 0) {
+    return state.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The Result Type of " << instr_name << " <id> '"
+           << state.getIdName(inst->id())
+           << "' must be OpTypeInt with width 32 and signedness 0.";
+  }
+
+  auto type_id = inst->GetOperandAs<uint32_t>(2);
+  auto type = state.FindDef(type_id);
+  if (type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    return state.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The type in " << instr_name << " <id> '"
+           << state.getIdName(type_id)
+           << "' must be OpTypeCooperativeMatrixNV.";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateCooperativeMatrixLoadStoreNV(ValidationState_t& _,
+                                                  const Instruction* inst) {
+  uint32_t type_id;
+  const char* opname;
+  if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
+    type_id = inst->type_id();
+    opname = "SpvOpCooperativeMatrixLoadNV";
+  } else {
+    // get Object operand's type
+    type_id = _.FindDef(inst->GetOperandAs<uint32_t>(1))->type_id();
+    opname = "SpvOpCooperativeMatrixStoreNV";
+  }
+
+  auto matrix_type = _.FindDef(type_id);
+
+  if (matrix_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "SpvOpCooperativeMatrixLoadNV Result Type <id> '"
+             << _.getIdName(type_id) << "' is not a cooperative matrix type.";
+    } else {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "SpvOpCooperativeMatrixStoreNV Object type <id> '"
+             << _.getIdName(type_id) << "' is not a cooperative matrix type.";
+    }
+  }
+
+  const bool uses_variable_pointers =
+      _.features().variable_pointers ||
+      _.features().variable_pointers_storage_buffer;
+  const auto pointer_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 2u : 0u;
+  const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
+  const auto pointer = _.FindDef(pointer_id);
+  if (!pointer ||
+      ((_.addressing_model() == SpvAddressingModelLogical) &&
+       ((!uses_variable_pointers &&
+         !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
+        (uses_variable_pointers &&
+         !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " Pointer <id> '" << _.getIdName(pointer_id)
+           << "' is not a logical pointer.";
+  }
+
+  const auto pointer_type_id = pointer->type_id();
+  const auto pointer_type = _.FindDef(pointer_type_id);
+  if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " type for pointer <id> '" << _.getIdName(pointer_id)
+           << "' is not a pointer type.";
+  }
+
+  const auto storage_class_index = 1u;
+  const auto storage_class =
+      pointer_type->GetOperandAs<uint32_t>(storage_class_index);
+
+  if (storage_class != SpvStorageClassWorkgroup &&
+      storage_class != SpvStorageClassStorageBuffer &&
+      storage_class != SpvStorageClassPhysicalStorageBufferEXT) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " storage class for pointer type <id> '"
+           << _.getIdName(pointer_type_id)
+           << "' is not Workgroup or StorageBuffer.";
+  }
+
+  const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
+  const auto pointee_type = _.FindDef(pointee_id);
+  if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
+                         _.IsFloatScalarOrVectorType(pointee_id))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " Pointer <id> '" << _.getIdName(pointer->id())
+           << "'s Type must be a scalar or vector type.";
+  }
+
+  const auto stride_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 3u : 2u;
+  const auto stride_id = inst->GetOperandAs<uint32_t>(stride_index);
+  const auto stride = _.FindDef(stride_id);
+  if (!stride || !_.IsIntScalarType(stride->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Stride operand <id> '" << _.getIdName(stride_id)
+           << "' must be a scalar integer type.";
+  }
+
+  const auto colmajor_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 4u : 3u;
+  const auto colmajor_id = inst->GetOperandAs<uint32_t>(colmajor_index);
+  const auto colmajor = _.FindDef(colmajor_id);
+  if (!colmajor || !_.IsBoolScalarType(colmajor->type_id()) ||
+      !(spvOpcodeIsConstant(colmajor->opcode()) ||
+        spvOpcodeIsSpecConstant(colmajor->opcode()))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Column Major operand <id> '" << _.getIdName(colmajor_id)
+           << "' must be a boolean constant instruction.";
+  }
+
+  const auto memory_access_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 5u : 4u;
+  if (inst->operands().size() > memory_access_index) {
+    if (auto error = CheckMemoryAccess(_, inst, memory_access_index))
+      return error;
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidatePtrComparison(ValidationState_t& _,
+                                   const Instruction* inst) {
+  if (_.addressing_model() == SpvAddressingModelLogical &&
+      !_.features().variable_pointers_storage_buffer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Instruction cannot be used without a variable pointers "
+              "capability";
+  }
+
+  const auto result_type = _.FindDef(inst->type_id());
+  if (inst->opcode() == SpvOpPtrDiff) {
+    if (!result_type || result_type->opcode() != SpvOpTypeInt) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Result Type must be an integer scalar";
+    }
+  } else {
+    if (!result_type || result_type->opcode() != SpvOpTypeBool) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Result Type must be OpTypeBool";
+    }
+  }
+
+  const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
+  const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
+  if (!op1 || !op2 || op1->type_id() != op2->type_id()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The types of Operand 1 and Operand 2 must match";
+  }
+  const auto op1_type = _.FindDef(op1->type_id());
+  if (!op1_type || op1_type->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Operand type must be a pointer";
+  }
+
+  if (_.addressing_model() == SpvAddressingModelLogical) {
+    SpvStorageClass sc = op1_type->GetOperandAs<SpvStorageClass>(1u);
+    if (sc != SpvStorageClassWorkgroup && sc != SpvStorageClassStorageBuffer) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Invalid pointer storage class";
+    }
+
+    if (sc == SpvStorageClassWorkgroup && !_.features().variable_pointers) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Workgroup storage class pointer requires VariablePointers "
+                "capability to be specified";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
@@ -1174,6 +1600,19 @@
     case SpvOpArrayLength:
       if (auto error = ValidateArrayLength(_, inst)) return error;
       break;
+    case SpvOpCooperativeMatrixLoadNV:
+    case SpvOpCooperativeMatrixStoreNV:
+      if (auto error = ValidateCooperativeMatrixLoadStoreNV(_, inst))
+        return error;
+      break;
+    case SpvOpCooperativeMatrixLengthNV:
+      if (auto error = ValidateCooperativeMatrixLengthNV(_, inst)) return error;
+      break;
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+    case SpvOpPtrDiff:
+      if (auto error = ValidatePtrComparison(_, inst)) return error;
+      break;
     case SpvOpImageTexelPointer:
     case SpvOpGenericPtrMemSemantics:
     default:
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
index f90b5e4..0088cdd 100644
--- a/source/val/validate_memory_semantics.cpp
+++ b/source/val/validate_memory_semantics.cpp
@@ -39,30 +39,54 @@
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
+    if (_.HasCapability(SpvCapabilityShader) &&
+        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Memory Semantics ids must be OpConstant when Shader "
                 "capability is present";
     }
+
+    if (_.HasCapability(SpvCapabilityShader) &&
+        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
+        !spvOpcodeIsConstant(_.GetIdOpcode(id))) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Memory Semantics must be a constant instruction when "
+                "CooperativeMatrixNV capability is present";
+    }
     return SPV_SUCCESS;
   }
 
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    uint32_t valid_bits = SpvMemorySemanticsAcquireMask |
-                          SpvMemorySemanticsReleaseMask |
-                          SpvMemorySemanticsAcquireReleaseMask |
-                          SpvMemorySemanticsUniformMemoryMask |
+    uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
                           SpvMemorySemanticsWorkgroupMemoryMask |
                           SpvMemorySemanticsImageMemoryMask |
                           SpvMemorySemanticsOutputMemoryKHRMask |
                           SpvMemorySemanticsMakeAvailableKHRMask |
                           SpvMemorySemanticsMakeVisibleKHRMask;
+    if (!spvOpcodeIsAtomicOp(inst->opcode())) {
+      valid_bits |= SpvMemorySemanticsAcquireReleaseMask;
+    }
+
     if (value & ~valid_bits) {
+      if (spvOpcodeIsAtomicOp(inst->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "WebGPU spec disallows, for OpAtomic*, any bit masks in "
+                  "Memory Semantics that are not UniformMemory, "
+                  "WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
+      } else {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "WebGPU spec disallows any bit masks in Memory Semantics "
+                  "that are not AcquireRelease, UniformMemory, "
+                  "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
+                  "MakeAvailableKHR, or MakeVisibleKHR";
+      }
+    }
+
+    if (!spvOpcodeIsAtomicOp(inst->opcode()) &&
+        !(value & SpvMemorySemanticsAcquireReleaseMask)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "WebGPU spec disallows any bit masks in Memory Semantics that "
-                "are not Acquire, Release, AcquireRelease, UniformMemory, "
-                "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
-                "MakeAvailableKHR, or MakeVisibleKHR";
+             << "WebGPU spec requires AcquireRelease to set in Memory "
+                "Semantics.";
     }
   }
 
@@ -112,6 +136,21 @@
            << "VulkanMemoryModelKHR";
   }
 
+  if (value & SpvMemorySemanticsVolatileMask) {
+    if (!_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": Memory Semantics Volatile requires capability "
+                "VulkanMemoryModelKHR";
+    }
+
+    if (!spvOpcodeIsAtomicOp(inst->opcode())) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Memory Semantics Volatile can only be used with atomic "
+                "instructions";
+    }
+  }
+
   if (value & SpvMemorySemanticsUniformMemoryMask &&
       !_.HasCapability(SpvCapabilityShader)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp
new file mode 100644
index 0000000..28e3fc6
--- /dev/null
+++ b/source/val/validate_misc.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2018 Google LLC.
+// Copyright (c) 2019 NVIDIA Corporation
+//
+// 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/val/validate.h"
+
+#include "source/opcode.h"
+#include "source/spirv_target_env.h"
+#include "source/val/instruction.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+spv_result_t ValidateUndef(ValidationState_t& _, const Instruction* inst) {
+  if (_.HasCapability(SpvCapabilityShader) &&
+      _.ContainsLimitedUseIntOrFloatType(inst->type_id()) &&
+      !_.IsPointerType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cannot create undefined values with 8- or 16-bit types";
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace
+
+spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpUndef:
+      if (auto error = ValidateUndef(_, inst)) return error;
+      break;
+    default:
+      break;
+  }
+  switch (inst->opcode()) {
+    case SpvOpBeginInvocationInterlockEXT:
+    case SpvOpEndInvocationInterlockEXT:
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              SpvExecutionModelFragment,
+              "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT "
+              "require Fragment execution model");
+
+      _.function(inst->function()->id())
+          ->RegisterLimitation([](const ValidationState_t& state,
+                                  const Function* entry_point,
+                                  std::string* message) {
+            const auto* execution_modes =
+                state.GetExecutionModes(entry_point->id());
+
+            auto find_interlock = [](const SpvExecutionMode& mode) {
+              switch (mode) {
+                case SpvExecutionModePixelInterlockOrderedEXT:
+                case SpvExecutionModePixelInterlockUnorderedEXT:
+                case SpvExecutionModeSampleInterlockOrderedEXT:
+                case SpvExecutionModeSampleInterlockUnorderedEXT:
+                case SpvExecutionModeShadingRateInterlockOrderedEXT:
+                case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+                  return true;
+                default:
+                  return false;
+              }
+            };
+
+            bool found = false;
+            if (execution_modes) {
+              auto i = std::find_if(execution_modes->begin(),
+                                    execution_modes->end(), find_interlock);
+              found = (i != execution_modes->end());
+            }
+
+            if (!found) {
+              *message =
+                  "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT "
+                  "require a fragment shader interlock execution mode.";
+              return false;
+            }
+            return true;
+          });
+      break;
+    case SpvOpDemoteToHelperInvocationEXT:
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              SpvExecutionModelFragment,
+              "OpDemoteToHelperInvocationEXT requires Fragment execution "
+              "model");
+      break;
+    case SpvOpIsHelperInvocationEXT: {
+      const uint32_t result_type = inst->type_id();
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              SpvExecutionModelFragment,
+              "OpIsHelperInvocationEXT requires Fragment execution model");
+      if (!_.IsBoolScalarType(result_type))
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected bool scalar type as Result Type: "
+               << spvOpcodeString(inst->opcode());
+      break;
+    }
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index c1bfc27..cbcf11a 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -33,12 +33,11 @@
            << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
            << "' is not a function.";
   }
-  // don't check kernel function signatures
+
+  // Only check the shader execution models
   const SpvExecutionModel execution_model =
       inst->GetOperandAs<SpvExecutionModel>(0);
   if (execution_model != SpvExecutionModelKernel) {
-    // TODO: Check the entry point signature is void main(void), may be subject
-    // to change
     const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3);
     const auto entry_point_type = _.FindDef(entry_point_type_id);
     if (!entry_point_type || 3 != entry_point_type->words().size()) {
@@ -91,6 +90,26 @@
                     "one of DepthGreater, DepthLess or DepthUnchanged "
                     "execution modes.";
         }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModePixelInterlockOrderedEXT:
+                        case SpvExecutionModePixelInterlockUnorderedEXT:
+                        case SpvExecutionModeSampleInterlockOrderedEXT:
+                        case SpvExecutionModeSampleInterlockUnorderedEXT:
+                        case SpvExecutionModeShadingRateInterlockOrderedEXT:
+                        case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one fragment shader interlock execution mode.";
+        }
         break;
       case SpvExecutionModelTessellationControl:
       case SpvExecutionModelTessellationEvaluation:
@@ -236,6 +255,36 @@
   }
 
   const auto mode = inst->GetOperandAs<SpvExecutionMode>(1);
+  if (inst->opcode() == SpvOpExecutionModeId) {
+    size_t operand_count = inst->operands().size();
+    for (size_t i = 2; i < operand_count; ++i) {
+      const auto operand_id = inst->GetOperandAs<uint32_t>(2);
+      const auto* operand_inst = _.FindDef(operand_id);
+      if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
+          mode == SpvExecutionModeLocalSizeHintId ||
+          mode == SpvExecutionModeLocalSizeId) {
+        if (!spvOpcodeIsConstant(operand_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For OpExecutionModeId all Extra Operand ids must be "
+                    "constant "
+                    "instructions.";
+        }
+      } else {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpExecutionModeId is only valid when the Mode operand is an "
+                  "execution mode that takes Extra Operands that are id "
+                  "operands.";
+      }
+    }
+  } else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
+             mode == SpvExecutionModeLocalSizeHintId ||
+             mode == SpvExecutionModeLocalSizeId) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "OpExecutionMode is only valid when the Mode operand is an "
+              "execution mode that takes no Extra Operands, or takes Extra "
+              "Operands that are not id operands.";
+  }
+
   const auto* models = _.GetExecutionModels(entry_point_id);
   switch (mode) {
     case SpvExecutionModeInvocations:
@@ -344,8 +393,15 @@
     case SpvExecutionModeOriginLowerLeft:
     case SpvExecutionModeEarlyFragmentTests:
     case SpvExecutionModeDepthReplacing:
+    case SpvExecutionModeDepthGreater:
     case SpvExecutionModeDepthLess:
     case SpvExecutionModeDepthUnchanged:
+    case SpvExecutionModePixelInterlockOrderedEXT:
+    case SpvExecutionModePixelInterlockUnorderedEXT:
+    case SpvExecutionModeSampleInterlockOrderedEXT:
+    case SpvExecutionModeSampleInterlockUnorderedEXT:
+    case SpvExecutionModeShadingRateInterlockOrderedEXT:
+    case SpvExecutionModeShadingRateInterlockUnorderedEXT:
       if (!std::all_of(models->begin(), models->end(),
                        [](const SpvExecutionModel& model) {
                          return model == SpvExecutionModelFragment;
@@ -411,6 +467,21 @@
     }
   }
 
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (mode != SpvExecutionModeOriginUpperLeft &&
+        mode != SpvExecutionModeDepthReplacing &&
+        mode != SpvExecutionModeDepthGreater &&
+        mode != SpvExecutionModeDepthLess &&
+        mode != SpvExecutionModeDepthUnchanged &&
+        mode != SpvExecutionModeLocalSize &&
+        mode != SpvExecutionModeLocalSizeHint) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Execution mode must be one of OriginUpperLeft, "
+                "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                "LocalSize, or LocalSizeHint for WebGPU environment.";
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index b640131..c607984 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -22,6 +22,23 @@
 namespace spvtools {
 namespace val {
 
+bool IsValidScope(uint32_t scope) {
+  // Deliberately avoid a default case so we have to update the list when the
+  // scopes list changes.
+  switch (static_cast<SpvScope>(scope)) {
+    case SpvScopeCrossDevice:
+    case SpvScopeDevice:
+    case SpvScopeWorkgroup:
+    case SpvScopeSubgroup:
+    case SpvScopeInvocation:
+    case SpvScopeQueueFamilyKHR:
+      return true;
+    case SpvScopeMax:
+      break;
+  }
+  return false;
+}
+
 spv_result_t ValidateExecutionScope(ValidationState_t& _,
                                     const Instruction* inst, uint32_t scope) {
   SpvOp opcode = inst->opcode();
@@ -36,14 +53,27 @@
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
+    if (_.HasCapability(SpvCapabilityShader) &&
+        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Scope ids must be OpConstant when Shader capability is "
              << "present";
     }
+    if (_.HasCapability(SpvCapabilityShader) &&
+        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
+        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be constant or specialization constant when "
+             << "CooperativeMatrixNV capability is present";
+    }
     return SPV_SUCCESS;
   }
 
+  if (is_const_int32 && !IsValidScope(value)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
+  }
+
   // Vulkan specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
     // Vulkan 1.1 specific rules
@@ -93,11 +123,11 @@
   // WebGPU Specific rules
   if (spvIsWebGPUEnv(_.context()->target_env)) {
     // Scope for execution must be limited to Workgroup or Subgroup
-    if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
+    if (value != SpvScopeWorkgroup) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in WebGPU environment Execution Scope is limited to "
-             << "Workgroup and Subgroup";
+             << "Workgroup";
     }
   }
 
@@ -130,14 +160,27 @@
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
+    if (_.HasCapability(SpvCapabilityShader) &&
+        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Scope ids must be OpConstant when Shader capability is "
              << "present";
     }
+    if (_.HasCapability(SpvCapabilityShader) &&
+        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
+        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be constant or specialization constant when "
+             << "CooperativeMatrixNV capability is present";
+    }
     return SPV_SUCCESS;
   }
 
+  if (is_const_int32 && !IsValidScope(value)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
+  }
+
   if (value == SpvScopeQueueFamilyKHR) {
     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
       return SPV_SUCCESS;
@@ -186,12 +229,12 @@
 
   // WebGPU specific rules
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup &&
+    if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
         value != SpvScopeQueueFamilyKHR) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in WebGPU environment Memory Scope is limited to "
-             << "Workgroup, Subgroup and QueuFamilyKHR";
+             << "Workgroup, Invocation, and QueueFamilyKHR";
     }
   }
 
diff --git a/source/val/validate_small_type_uses.cpp b/source/val/validate_small_type_uses.cpp
new file mode 100644
index 0000000..9db82e7
--- /dev/null
+++ b/source/val/validate_small_type_uses.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/val/validate.h"
+
+#include "source/val/instruction.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t ValidateSmallTypeUses(ValidationState_t& _,
+                                   const Instruction* inst) {
+  if (!_.HasCapability(SpvCapabilityShader) || inst->type_id() == 0 ||
+      !_.ContainsLimitedUseIntOrFloatType(inst->type_id())) {
+    return SPV_SUCCESS;
+  }
+
+  if (_.IsPointerType(inst->type_id())) return SPV_SUCCESS;
+
+  // The validator should previously have checked ways to generate 8- or 16-bit
+  // types. So we only need to considervalid paths from source to sink.
+  // When restricted, uses of 8- or 16-bit types can only be stores,
+  // width-only conversions, decorations and copy object.
+  for (auto use : inst->uses()) {
+    const auto* user = use.first;
+    switch (user->opcode()) {
+      case SpvOpDecorate:
+      case SpvOpDecorateId:
+      case SpvOpCopyObject:
+      case SpvOpStore:
+      case SpvOpFConvert:
+      case SpvOpUConvert:
+      case SpvOpSConvert:
+        break;
+      default:
+        return _.diag(SPV_ERROR_INVALID_ID, user)
+               << "Invalid use of 8- or 16-bit result";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 94ea660..afc0656 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -14,11 +14,10 @@
 
 // Ensures type declarations are unique unless allowed by the specification.
 
-#include "source/val/validate.h"
-
 #include "source/opcode.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -67,6 +66,16 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) {
+  const auto signedness_index = 2;
+  const auto signedness = inst->GetOperandAs<uint32_t>(signedness_index);
+  if (signedness != 0 && signedness != 1) {
+    return _.diag(SPV_ERROR_INVALID_VALUE, inst)
+           << "OpTypeInt has invalid signedness:";
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) {
   const auto component_index = 1;
   const auto component_id = inst->GetOperandAs<uint32_t>(component_index);
@@ -107,14 +116,12 @@
            << "' is a void type.";
   }
 
-  if ((spvIsVulkanEnv(_.context()->target_env) ||
-       spvIsWebGPUEnv(_.context()->target_env)) &&
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
       element_type->opcode() == SpvOpTypeRuntimeArray) {
-    const char* env_text =
-        spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypeArray Element Type <id> '" << _.getIdName(element_type_id)
-           << "' is not valid in " << env_text << " environment.";
+           << "' is not valid in "
+           << spvLogStringForEnv(_.context()->target_env) << " environments.";
   }
 
   const auto length_index = 2;
@@ -172,20 +179,46 @@
            << _.getIdName(element_id) << "' is a void type.";
   }
 
-  if ((spvIsVulkanEnv(_.context()->target_env) ||
-       spvIsWebGPUEnv(_.context()->target_env)) &&
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
       element_type->opcode() == SpvOpTypeRuntimeArray) {
-    const char* env_text =
-        spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypeRuntimeArray Element Type <id> '"
-           << _.getIdName(element_id) << "' is not valid in " << env_text
-           << " environment.";
+           << _.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;
@@ -229,21 +262,42 @@
       }
     }
 
-    if ((spvIsVulkanEnv(_.context()->target_env) ||
-         spvIsWebGPUEnv(_.context()->target_env)) &&
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
         member_type->opcode() == SpvOpTypeRuntimeArray) {
       const bool is_last_member =
           member_type_index == inst->operands().size() - 1;
       if (!is_last_member) {
-        const char* env_text =
-            spvIsVulkanEnv(_.context()->target_env) ? "Vulkan" : "WebGPU";
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "In " << env_text << ", OpTypeRuntimeArray must only be used "
-               << "for the last member of an OpTypeStruct";
+               << "In " << spvLogStringForEnv(_.context()->target_env)
+               << ", OpTypeRuntimeArray must only be used for the last member "
+                  "of an OpTypeStruct";
       }
     }
   }
 
+  bool has_nested_blockOrBufferBlock_struct = false;
+  // Struct members start at word 2 of OpTypeStruct instruction.
+  for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) {
+    auto member = inst->word(word_i);
+    auto memberTypeInstr = _.FindDef(member);
+    if (memberTypeInstr && SpvOpTypeStruct == memberTypeInstr->opcode()) {
+      if (_.HasDecoration(memberTypeInstr->id(), SpvDecorationBlock) ||
+          _.HasDecoration(memberTypeInstr->id(), SpvDecorationBufferBlock) ||
+          _.GetHasNestedBlockOrBufferBlockStruct(memberTypeInstr->id()))
+        has_nested_blockOrBufferBlock_struct = true;
+    }
+  }
+
+  _.SetHasNestedBlockOrBufferBlockStruct(inst->id(),
+                                         has_nested_blockOrBufferBlock_struct);
+  if (_.GetHasNestedBlockOrBufferBlockStruct(inst->id()) &&
+      (_.HasDecoration(inst->id(), SpvDecorationBufferBlock) ||
+       _.HasDecoration(inst->id(), SpvDecorationBlock))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "rules: A Block or BufferBlock cannot be nested within another "
+              "Block or BufferBlock. ";
+  }
+
   std::unordered_set<uint32_t> built_in_members;
   for (auto decoration : _.id_decorations(struct_id)) {
     if (decoration.dec_type() == SpvDecorationBuiltIn &&
@@ -264,20 +318,45 @@
   if (num_builtin_members > 0) {
     _.RegisterStructTypeWithBuiltInMember(struct_id);
   }
+
+  if (spvIsVulkanEnv(_.context()->target_env) &&
+      !_.options()->before_hlsl_legalization && ContainsOpaqueType(_, inst)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "In " << spvLogStringForEnv(_.context()->target_env)
+           << ", OpTypeStruct must not contain an opaque type.";
+  }
+
   return SPV_SUCCESS;
 }
 
 spv_result_t ValidateTypePointer(ValidationState_t& _,
                                  const Instruction* inst) {
-  const auto type_id = inst->GetOperandAs<uint32_t>(2);
-  const auto type = _.FindDef(type_id);
+  auto type_id = inst->GetOperandAs<uint32_t>(2);
+  auto type = _.FindDef(type_id);
   if (!type || !spvOpcodeGeneratesType(type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypePointer Type <id> '" << _.getIdName(type_id)
            << "' is not a type.";
   }
+  // See if this points to a storage image.
+  const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
+  if (storage_class == SpvStorageClassUniformConstant) {
+    // Unpack an optional level of arraying.
+    if (type->opcode() == SpvOpTypeArray ||
+        type->opcode() == SpvOpTypeRuntimeArray) {
+      type_id = type->GetOperandAs<uint32_t>(1);
+      type = _.FindDef(type_id);
+    }
+    if (type->opcode() == SpvOpTypeImage) {
+      const auto sampled = type->GetOperandAs<uint32_t>(6);
+      // 2 indicates this image is known to be be used without a sampler, i.e.
+      // a storage image.
+      if (sampled == 2) _.RegisterPointerToStorageImage(inst->id());
+    }
+  }
   return SPV_SUCCESS;
 }
+}  // namespace
 
 spv_result_t ValidateTypeFunction(ValidationState_t& _,
                                   const Instruction* inst) {
@@ -315,10 +394,12 @@
            << num_args << " arguments.";
   }
 
-  // The only valid uses of OpTypeFunction are in an OpFunction instruction.
+  // The only valid uses of OpTypeFunction are in an OpFunction, debugging, or
+  // decoration instruction.
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
-    if (use->opcode() != SpvOpFunction) {
+    if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) &&
+        !spvOpcodeIsDecoration(use->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function type result id "
              << _.getIdName(inst->id()) << ".";
@@ -347,7 +428,52 @@
   return SPV_SUCCESS;
 }
 
-}  // namespace
+spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _,
+                                             const Instruction* inst) {
+  const auto component_type_index = 1;
+  const auto component_type_id =
+      inst->GetOperandAs<uint32_t>(component_type_index);
+  const auto component_type = _.FindDef(component_type_id);
+  if (!component_type || (SpvOpTypeFloat != component_type->opcode() &&
+                          SpvOpTypeInt != component_type->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Component Type <id> '"
+           << _.getIdName(component_type_id)
+           << "' is not a scalar numerical type.";
+  }
+
+  const auto scope_index = 2;
+  const auto scope_id = inst->GetOperandAs<uint32_t>(scope_index);
+  const auto scope = _.FindDef(scope_id);
+  if (!scope || !_.IsIntScalarType(scope->type_id()) ||
+      !spvOpcodeIsConstant(scope->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Scope <id> '" << _.getIdName(scope_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  const auto rows_index = 3;
+  const auto rows_id = inst->GetOperandAs<uint32_t>(rows_index);
+  const auto rows = _.FindDef(rows_id);
+  if (!rows || !_.IsIntScalarType(rows->type_id()) ||
+      !spvOpcodeIsConstant(rows->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Rows <id> '" << _.getIdName(rows_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  const auto cols_index = 4;
+  const auto cols_id = inst->GetOperandAs<uint32_t>(cols_index);
+  const auto cols = _.FindDef(cols_id);
+  if (!cols || !_.IsIntScalarType(cols->type_id()) ||
+      !spvOpcodeIsConstant(cols->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(rows_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  return SPV_SUCCESS;
+}
 
 spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
   if (!spvOpcodeGeneratesType(inst->opcode()) &&
@@ -358,6 +484,9 @@
   if (auto error = ValidateUniqueness(_, inst)) return error;
 
   switch (inst->opcode()) {
+    case SpvOpTypeInt:
+      if (auto error = ValidateTypeInt(_, inst)) return error;
+      break;
     case SpvOpTypeVector:
       if (auto error = ValidateTypeVector(_, inst)) return error;
       break;
@@ -382,6 +511,9 @@
     case SpvOpTypeForwardPointer:
       if (auto error = ValidateTypeForwardPointer(_, inst)) return error;
       break;
+    case SpvOpTypeCooperativeMatrixNV:
+      if (auto error = ValidateTypeCooperativeMatrixNV(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 4a42935..794d0f7 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/val/basic_block.h"
 #include "source/val/construct.h"
@@ -146,6 +147,30 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
+                       uint32_t version, uint32_t generator, uint32_t id_bound,
+                       uint32_t) {
+  ValidationState_t& vstate =
+      *(reinterpret_cast<ValidationState_t*>(user_data));
+  vstate.setIdBound(id_bound);
+  vstate.setGenerator(generator);
+  vstate.setVersion(version);
+
+  return SPV_SUCCESS;
+}
+
+// Add features based on SPIR-V core version number.
+void UpdateFeaturesBasedOnSpirvVersion(ValidationState_t::Feature* features,
+                                       uint32_t version) {
+  assert(features);
+  if (version >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    features->select_between_composites = true;
+    features->copy_memory_permits_two_memory_accesses = true;
+    features->uconvert_spec_constant_op = true;
+    features->nonwritable_var_in_function_or_private = true;
+  }
+}
+
 }  // namespace
 
 ValidationState_t::ValidationState_t(const spv_const_context ctx,
@@ -168,6 +193,7 @@
       global_vars_(),
       local_vars_(),
       struct_nesting_depth_(),
+      struct_has_nested_blockorbufferblock_struct_(),
       grammar_(ctx),
       addressing_model_(SpvAddressingModelMax),
       memory_model_(SpvMemoryModelMax),
@@ -204,11 +230,12 @@
     spv_context_t hijacked_context = *ctx;
     hijacked_context.consumer = [](spv_message_level_t, const char*,
                                    const spv_position_t&, const char*) {};
-    spvBinaryParse(&hijacked_context, this, words, num_words,
-                   /* parsed_header = */ nullptr, CountInstructions,
+    spvBinaryParse(&hijacked_context, this, words, num_words, setHeader,
+                   CountInstructions,
                    /* diagnostic = */ nullptr);
     preallocateStorage();
   }
+  UpdateFeaturesBasedOnSpirvVersion(&features_, version_);
 
   friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
       context_, words_, num_words_);
@@ -446,7 +473,7 @@
       pointer_size_and_alignment_ = 4;
       break;
     default:
-      // fall through
+    // fall through
     case SpvAddressingModelPhysical64:
     case SpvAddressingModelPhysicalStorageBuffer64EXT:
       pointer_size_and_alignment_ = 8;
@@ -539,15 +566,15 @@
       const uint32_t operand_word = inst->word(operand.offset);
       Instruction* operand_inst = FindDef(operand_word);
       if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) {
-        RegisterSampledImageConsumer(operand_word, inst->id());
+        RegisterSampledImageConsumer(operand_word, inst);
       }
     }
   }
 }
 
-std::vector<uint32_t> ValidationState_t::getSampledImageConsumers(
+std::vector<Instruction*> ValidationState_t::getSampledImageConsumers(
     uint32_t sampled_image_id) const {
-  std::vector<uint32_t> result;
+  std::vector<Instruction*> result;
   auto iter = sampled_image_consumers_.find(sampled_image_id);
   if (iter != sampled_image_consumers_.end()) {
     result = iter->second;
@@ -556,8 +583,8 @@
 }
 
 void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id,
-                                                     uint32_t consumer_id) {
-  sampled_image_consumers_[sampled_image_id].push_back(consumer_id);
+                                                     Instruction* consumer) {
+  sampled_image_consumers_[sampled_image_id].push_back(consumer);
 }
 
 uint32_t ValidationState_t::getIdBound() const { return id_bound_; }
@@ -609,6 +636,9 @@
     case SpvOpTypeMatrix:
       return GetComponentType(inst->word(2));
 
+    case SpvOpTypeCooperativeMatrixNV:
+      return inst->word(2);
+
     default:
       break;
   }
@@ -633,6 +663,10 @@
     case SpvOpTypeMatrix:
       return inst->word(3);
 
+    case SpvOpTypeCooperativeMatrixNV:
+      // Actual dimension isn't known, return 0
+      return 0;
+
     default:
       break;
   }
@@ -657,6 +691,12 @@
   return 0;
 }
 
+bool ValidationState_t::IsVoidType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeVoid;
+}
+
 bool ValidationState_t::IsFloatScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
   assert(inst);
@@ -861,6 +901,86 @@
   return true;
 }
 
+bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeCooperativeMatrixNV;
+}
+
+bool ValidationState_t::IsFloatCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsFloatScalarType(FindDef(id)->word(2));
+}
+
+bool ValidationState_t::IsIntCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsIntScalarType(FindDef(id)->word(2));
+}
+
+bool ValidationState_t::IsUnsignedIntCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsUnsignedIntScalarType(FindDef(id)->word(2));
+}
+
+spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
+    const Instruction* inst, uint32_t m1, uint32_t m2) {
+  const auto m1_type = FindDef(m1);
+  const auto m2_type = FindDef(m2);
+
+  if (m1_type->opcode() != SpvOpTypeCooperativeMatrixNV ||
+      m2_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected cooperative matrix types";
+  }
+
+  uint32_t m1_scope_id = m1_type->GetOperandAs<uint32_t>(2);
+  uint32_t m1_rows_id = m1_type->GetOperandAs<uint32_t>(3);
+  uint32_t m1_cols_id = m1_type->GetOperandAs<uint32_t>(4);
+
+  uint32_t m2_scope_id = m2_type->GetOperandAs<uint32_t>(2);
+  uint32_t m2_rows_id = m2_type->GetOperandAs<uint32_t>(3);
+  uint32_t m2_cols_id = m2_type->GetOperandAs<uint32_t>(4);
+
+  bool m1_is_int32 = false, m1_is_const_int32 = false, m2_is_int32 = false,
+       m2_is_const_int32 = false;
+  uint32_t m1_value = 0, m2_value = 0;
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_scope_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_scope_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected scopes of Matrix and Result Type to be "
+           << "identical";
+  }
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_rows_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_rows_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected rows of Matrix type and Result Type to be "
+           << "identical";
+  }
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_cols_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_cols_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected columns of Matrix type and Result Type to be "
+           << "identical";
+  }
+
+  return SPV_SUCCESS;
+}
+
 uint32_t ValidationState_t::GetOperandTypeId(const Instruction* inst,
                                              size_t operand_index) const {
   return GetTypeId(inst->GetOperandAs<uint32_t>(operand_index));
@@ -889,7 +1009,7 @@
 }
 
 std::tuple<bool, bool, uint32_t> ValidationState_t::EvalInt32IfConst(
-    uint32_t id) {
+    uint32_t id) const {
   const Instruction* const inst = FindDef(id);
   assert(inst);
   const uint32_t type = inst->type_id();
@@ -1021,5 +1141,141 @@
                                     words_, num_words_, disassembly_options);
 }
 
+bool ValidationState_t::LogicallyMatch(const Instruction* lhs,
+                                       const Instruction* rhs,
+                                       bool check_decorations) {
+  if (lhs->opcode() != rhs->opcode()) {
+    return false;
+  }
+
+  if (check_decorations) {
+    const auto& dec_a = id_decorations(lhs->id());
+    const auto& dec_b = id_decorations(rhs->id());
+
+    for (const auto& dec : dec_b) {
+      if (std::find(dec_a.begin(), dec_a.end(), dec) == dec_a.end()) {
+        return false;
+      }
+    }
+  }
+
+  if (lhs->opcode() == SpvOpTypeArray) {
+    // Size operands must match.
+    if (lhs->GetOperandAs<uint32_t>(2u) != rhs->GetOperandAs<uint32_t>(2u)) {
+      return false;
+    }
+
+    // Elements must match or logically match.
+    const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(1u);
+    const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(1u);
+    if (lhs_ele_id == rhs_ele_id) {
+      return true;
+    }
+
+    const auto lhs_ele = FindDef(lhs_ele_id);
+    const auto rhs_ele = FindDef(rhs_ele_id);
+    if (!lhs_ele || !rhs_ele) {
+      return false;
+    }
+    return LogicallyMatch(lhs_ele, rhs_ele, check_decorations);
+  } else if (lhs->opcode() == SpvOpTypeStruct) {
+    // Number of elements must match.
+    if (lhs->operands().size() != rhs->operands().size()) {
+      return false;
+    }
+
+    for (size_t i = 1u; i < lhs->operands().size(); ++i) {
+      const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(i);
+      const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(i);
+      // Elements must match or logically match.
+      if (lhs_ele_id == rhs_ele_id) {
+        continue;
+      }
+
+      const auto lhs_ele = FindDef(lhs_ele_id);
+      const auto rhs_ele = FindDef(rhs_ele_id);
+      if (!lhs_ele || !rhs_ele) {
+        return false;
+      }
+
+      if (!LogicallyMatch(lhs_ele, rhs_ele, check_decorations)) {
+        return false;
+      }
+    }
+
+    // All checks passed.
+    return true;
+  }
+
+  // No other opcodes are acceptable at this point. Arrays and structs are
+  // caught above and if they're elements are not arrays or structs they are
+  // required to match exactly.
+  return false;
+}
+
+const Instruction* ValidationState_t::TracePointer(
+    const Instruction* inst) const {
+  auto base_ptr = inst;
+  while (base_ptr->opcode() == SpvOpAccessChain ||
+         base_ptr->opcode() == SpvOpInBoundsAccessChain ||
+         base_ptr->opcode() == SpvOpPtrAccessChain ||
+         base_ptr->opcode() == SpvOpInBoundsPtrAccessChain ||
+         base_ptr->opcode() == SpvOpCopyObject) {
+    base_ptr = FindDef(base_ptr->GetOperandAs<uint32_t>(2u));
+  }
+  return base_ptr;
+}
+
+bool ValidationState_t::ContainsSizedIntOrFloatType(uint32_t id, SpvOp type,
+                                                    uint32_t width) const {
+  if (type != SpvOpTypeInt && type != SpvOpTypeFloat) return false;
+
+  const auto inst = FindDef(id);
+  if (!inst) return false;
+
+  if (inst->opcode() == type) {
+    return inst->GetOperandAs<uint32_t>(1u) == width;
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+    case SpvOpTypeVector:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeImage:
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeCooperativeMatrixNV:
+      return ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(1u), type,
+                                         width);
+    case SpvOpTypePointer:
+      if (IsForwardPointer(id)) return false;
+      return ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(2u), type,
+                                         width);
+    case SpvOpTypeFunction:
+    case SpvOpTypeStruct: {
+      for (uint32_t i = 1; i < inst->operands().size(); ++i) {
+        if (ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(i), type,
+                                        width))
+          return true;
+      }
+      return false;
+    }
+    default:
+      return false;
+  }
+}
+
+bool ValidationState_t::ContainsLimitedUseIntOrFloatType(uint32_t id) const {
+  if ((!HasCapability(SpvCapabilityInt16) &&
+       ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 16)) ||
+      (!HasCapability(SpvCapabilityInt8) &&
+       ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 8)) ||
+      (!HasCapability(SpvCapabilityFloat16) &&
+       ContainsSizedIntOrFloatType(id, SpvOpTypeFloat, 16))) {
+    return true;
+  }
+  return false;
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 2c94906..ad16bcb 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -106,9 +106,20 @@
     // Members need not be listed in offset order
     bool scalar_block_layout = false;
 
-    // Permit UConvert as an OpSpecConstantOp operation.
+    // SPIR-V 1.4 allows us to select between any two composite values
+    // of the same type.
+    bool select_between_composites = false;
+
+    // SPIR-V 1.4 allows two memory access operands for OpCopyMemory and
+    // OpCopyMemorySized.
+    bool copy_memory_permits_two_memory_accesses = false;
+
+    // SPIR-V 1.4 allows UConvert as a spec constant op in any environment.
     // The Kernel capability already enables it, separately from this flag.
     bool uconvert_spec_constant_op = false;
+
+    // SPIR-V 1.4 allows Function and Private variables to be NonWritable
+    bool nonwritable_var_in_function_or_private = false;
   };
 
   ValidationState_t(const spv_const_context context,
@@ -432,13 +443,13 @@
     return all_definitions_;
   }
 
-  /// Returns a vector containing the Ids of instructions that consume the given
+  /// Returns a vector containing the instructions that consume the given
   /// SampledImage id.
-  std::vector<uint32_t> getSampledImageConsumers(uint32_t id) const;
+  std::vector<Instruction*> getSampledImageConsumers(uint32_t id) const;
 
   /// Records cons_id as a consumer of sampled_image_id.
   void RegisterSampledImageConsumer(uint32_t sampled_image_id,
-                                    uint32_t cons_id);
+                                    Instruction* consumer);
 
   /// Returns the set of Global Variables.
   std::unordered_set<uint32_t>& global_vars() { return global_vars_; }
@@ -474,6 +485,18 @@
     return struct_nesting_depth_[id];
   }
 
+  /// Records the has a nested block/bufferblock decorated struct for a given
+  /// struct ID
+  void SetHasNestedBlockOrBufferBlockStruct(uint32_t id, bool has) {
+    struct_has_nested_blockorbufferblock_struct_[id] = has;
+  }
+
+  /// For a given struct ID returns true if it has a nested block/bufferblock
+  /// decorated struct
+  bool GetHasNestedBlockOrBufferBlockStruct(uint32_t id) {
+    return struct_has_nested_blockorbufferblock_struct_[id];
+  }
+
   /// Records that the structure type has a member decorated with a built-in.
   void RegisterStructTypeWithBuiltInMember(uint32_t id) {
     builtin_structs_.insert(id);
@@ -525,6 +548,7 @@
 
   // Returns true iff |id| is a type corresponding to the name of the function.
   // Only works for types not for objects.
+  bool IsVoidType(uint32_t id) const;
   bool IsFloatScalarType(uint32_t id) const;
   bool IsFloatVectorType(uint32_t id) const;
   bool IsFloatScalarOrVectorType(uint32_t id) const;
@@ -540,6 +564,18 @@
   bool IsBoolVectorType(uint32_t id) const;
   bool IsBoolScalarOrVectorType(uint32_t id) const;
   bool IsPointerType(uint32_t id) const;
+  bool IsCooperativeMatrixType(uint32_t id) const;
+  bool IsFloatCooperativeMatrixType(uint32_t id) const;
+  bool IsIntCooperativeMatrixType(uint32_t id) const;
+  bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const;
+
+  // Returns true if |id| is a type id that contains |type| (or integer or
+  // floating point type) of |width| bits.
+  bool ContainsSizedIntOrFloatType(uint32_t id, SpvOp type,
+                                   uint32_t width) const;
+  // Returns true if |id| is a type id that contains a 8- or 16-bit int or
+  // 16-bit float that is not generally enabled for use.
+  bool ContainsLimitedUseIntOrFloatType(uint32_t id) const;
 
   // Gets value from OpConstant and OpSpecConstant as uint64.
   // Returns false on failure (no instruction, wrong instruction, not int).
@@ -562,11 +598,68 @@
   bool GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
                           uint32_t* storage_class) const;
 
+  // Is the ID the type of a pointer to a uniform block: Block-decorated struct
+  // in uniform storage class? The result is only valid after internal method
+  // CheckDecorationsOfBuffers has been called.
+  bool IsPointerToUniformBlock(uint32_t type_id) const {
+    return pointer_to_uniform_block_.find(type_id) !=
+           pointer_to_uniform_block_.cend();
+  }
+  // Save the ID of a pointer to uniform block.
+  void RegisterPointerToUniformBlock(uint32_t type_id) {
+    pointer_to_uniform_block_.insert(type_id);
+  }
+  // Is the ID the type of a struct used as a uniform block?
+  // The result is only valid after internal method CheckDecorationsOfBuffers
+  // has been called.
+  bool IsStructForUniformBlock(uint32_t type_id) const {
+    return struct_for_uniform_block_.find(type_id) !=
+           struct_for_uniform_block_.cend();
+  }
+  // Save the ID of a struct of a uniform block.
+  void RegisterStructForUniformBlock(uint32_t type_id) {
+    struct_for_uniform_block_.insert(type_id);
+  }
+  // Is the ID the type of a pointer to a storage buffer: BufferBlock-decorated
+  // struct in uniform storage class, or Block-decorated struct in StorageBuffer
+  // storage class? The result is only valid after internal method
+  // CheckDecorationsOfBuffers has been called.
+  bool IsPointerToStorageBuffer(uint32_t type_id) const {
+    return pointer_to_storage_buffer_.find(type_id) !=
+           pointer_to_storage_buffer_.cend();
+  }
+  // Save the ID of a pointer to a storage buffer.
+  void RegisterPointerToStorageBuffer(uint32_t type_id) {
+    pointer_to_storage_buffer_.insert(type_id);
+  }
+  // Is the ID the type of a struct for storage buffer?
+  // The result is only valid after internal method CheckDecorationsOfBuffers
+  // has been called.
+  bool IsStructForStorageBuffer(uint32_t type_id) const {
+    return struct_for_storage_buffer_.find(type_id) !=
+           struct_for_storage_buffer_.cend();
+  }
+  // Save the ID of a struct of a storage buffer.
+  void RegisterStructForStorageBuffer(uint32_t type_id) {
+    struct_for_storage_buffer_.insert(type_id);
+  }
+
+  // Is the ID the type of a pointer to a storage image?  That is, the pointee
+  // type is an image type which is known to not use a sampler.
+  bool IsPointerToStorageImage(uint32_t type_id) const {
+    return pointer_to_storage_image_.find(type_id) !=
+           pointer_to_storage_image_.cend();
+  }
+  // Save the ID of a pointer to a storage image.
+  void RegisterPointerToStorageImage(uint32_t type_id) {
+    pointer_to_storage_image_.insert(type_id);
+  }
+
   // Tries to evaluate a 32-bit signed or unsigned scalar integer constant.
   // Returns tuple <is_int32, is_const_int32, value>.
   // OpSpecConstant* return |is_const_int32| as false since their values cannot
   // be relied upon during validation.
-  std::tuple<bool, bool, uint32_t> EvalInt32IfConst(uint32_t id);
+  std::tuple<bool, bool, uint32_t> EvalInt32IfConst(uint32_t id) const;
 
   // Returns the disassembly string for the given instruction.
   std::string Disassemble(const Instruction& inst) const;
@@ -574,6 +667,36 @@
   // Returns the disassembly string for the given instruction.
   std::string Disassemble(const uint32_t* words, uint16_t num_words) const;
 
+  // Returns whether type m1 and type m2 are cooperative matrices with
+  // the same "shape" (matching scope, rows, cols). If any are specialization
+  // constants, we assume they can match because we can't prove they don't.
+  spv_result_t CooperativeMatrixShapesMatch(const Instruction* inst,
+                                            uint32_t m1, uint32_t m2);
+
+  // Returns true if |lhs| and |rhs| logically match and, if the decorations of
+  // |rhs| are a subset of |lhs|.
+  //
+  // 1. Must both be either OpTypeArray or OpTypeStruct
+  // 2. If OpTypeArray, then
+  //  * Length must be the same
+  //  * Element type must match or logically match
+  // 3. If OpTypeStruct, then
+  //  * Both have same number of elements
+  //  * Element N for both structs must match or logically match
+  //
+  // If |check_decorations| is false, then the decorations are not checked.
+  bool LogicallyMatch(const Instruction* lhs, const Instruction* rhs,
+                      bool check_decorations);
+
+  // Traces |inst| to find a single base pointer. Returns the base pointer.
+  // Will trace through the following instructions:
+  // * OpAccessChain
+  // * OpInBoundsAccessChain
+  // * OpPtrAccessChain
+  // * OpInBoundsPtrAccessChain
+  // * OpCopyObject
+  const Instruction* TracePointer(const Instruction* inst) const;
+
  private:
   ValidationState_t(const ValidationState_t&);
 
@@ -605,7 +728,8 @@
 
   /// Stores a vector of instructions that use the result of a given
   /// OpSampledImage instruction.
-  std::unordered_map<uint32_t, std::vector<uint32_t>> sampled_image_consumers_;
+  std::unordered_map<uint32_t, std::vector<Instruction*>>
+      sampled_image_consumers_;
 
   /// A map of operand IDs and their names defined by the OpName instruction
   std::unordered_map<uint32_t, std::string> operand_names_;
@@ -659,6 +783,10 @@
   /// Structure Nesting Depth
   std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_;
 
+  /// Structure has nested blockorbufferblock struct
+  std::unordered_map<uint32_t, bool>
+      struct_has_nested_blockorbufferblock_struct_;
+
   /// Stores the list of decorations for a given <id>
   std::map<uint32_t, std::vector<Decoration>> id_decorations_;
 
@@ -701,6 +829,23 @@
   std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
   const std::vector<uint32_t> empty_ids_;
 
+  // The IDs of types of pointers to Block-decorated structs in Uniform storage
+  // class. This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> pointer_to_uniform_block_;
+  // The IDs of struct types for uniform blocks.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> struct_for_uniform_block_;
+  // The IDs of types of pointers to BufferBlock-decorated structs in Uniform
+  // storage class, or Block-decorated structs in StorageBuffer storage class.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> pointer_to_storage_buffer_;
+  // The IDs of struct types for storage buffers.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> struct_for_storage_buffer_;
+  // The IDs of types of pointers to storage images.  This is populated in the
+  // TypePass.
+  std::unordered_set<uint32_t> pointer_to_storage_image_;
+
   /// Maps ids to friendly names.
   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
   spvtools::NameMapper name_mapper_;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 9226ea7..3dca430 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -129,6 +129,7 @@
   text_start_new_inst_test.cpp
   text_to_binary.annotation_test.cpp
   text_to_binary.barrier_test.cpp
+  text_to_binary.composite_test.cpp
   text_to_binary.constant_test.cpp
   text_to_binary.control_flow_test.cpp
   text_to_binary_test.cpp
@@ -182,33 +183,10 @@
 endif()
 
 
-add_spvtools_unittest(
-  TARGET bit_stream
-  SRCS bit_stream.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/bit_stream.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/bit_stream.h
-  LIBS ${SPIRV_TOOLS})
-
-add_spvtools_unittest(
-  TARGET huffman_codec
-  SRCS huffman_codec.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/bit_stream.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/bit_stream.h
-      ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/huffman_codec.h
-  LIBS ${SPIRV_TOOLS})
-
-add_spvtools_unittest(
-  TARGET move_to_front
-  SRCS move_to_front_test.cpp
-    ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/move_to_front.h
-    ${CMAKE_CURRENT_SOURCE_DIR}/../source/comp/move_to_front.cpp
-  LIBS ${SPIRV_TOOLS})
-
-add_subdirectory(comp)
 add_subdirectory(link)
 add_subdirectory(opt)
 add_subdirectory(reduce)
-add_subdirectory(stats)
+add_subdirectory(fuzz)
 add_subdirectory(tools)
 add_subdirectory(util)
 add_subdirectory(val)
diff --git a/test/assembly_context_test.cpp b/test/assembly_context_test.cpp
index b6d60b9..ee0bb24 100644
--- a/test/assembly_context_test.cpp
+++ b/test/assembly_context_test.cpp
@@ -46,7 +46,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryEncodeString, EncodeStringTest,
     ::testing::ValuesIn(std::vector<EncodeStringCase>{
       // Use cases that exercise at least one to two words,
@@ -70,7 +70,7 @@
       // A very long string, encoded after an initial word.
       // SPIR-V limits strings to 65535 characters.
       {std::string(65535, 'a'), {1}},
-    }),);
+    }));
 // clang-format on
 
 }  // namespace
diff --git a/test/binary_header_get_test.cpp b/test/binary_header_get_test.cpp
index e771f1a..dcaf992 100644
--- a/test/binary_header_get_test.cpp
+++ b/test/binary_header_get_test.cpp
@@ -51,7 +51,7 @@
   ASSERT_EQ(SPV_SUCCESS, spvBinaryHeaderGet(&const_bin, endian, &header));
 
   ASSERT_EQ(static_cast<uint32_t>(SpvMagicNumber), header.magic);
-  ASSERT_EQ(0x00010300u, header.version);
+  ASSERT_EQ(0x00010400u, header.version);
   ASSERT_EQ(static_cast<uint32_t>(SPV_GENERATOR_CODEPLAY), header.generator);
   ASSERT_EQ(1u, header.bound);
   ASSERT_EQ(0u, header.schema);
diff --git a/test/binary_parse_test.cpp b/test/binary_parse_test.cpp
index 749e12f..b966102 100644
--- a/test/binary_parse_test.cpp
+++ b/test/binary_parse_test.cpp
@@ -561,7 +561,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
     ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
         {nullptr, 0, "Missing module."},
@@ -575,7 +575,7 @@
          "Module has incomplete header: only 3 words instead of 5"},
         {kHeaderForBound1, 4,
          "Module has incomplete header: only 4 words instead of 5"},
-    }), );
+    }));
 
 // A binary parser diagnostic test case where a vector of words is
 // provided.  We'll use this to express cases that can't be created
@@ -598,7 +598,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
     ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
         {Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}),
@@ -816,7 +816,7 @@
              MakeInstruction(SpvOpConstant, {1, 2, 42}),
          }),
          "Type Id 1 is not a scalar numeric type"},
-    }), );
+    }));
 
 // A binary parser diagnostic case generated from an assembly text input.
 struct AssemblyDiagnosticCase {
@@ -836,7 +836,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
     ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
         {"%1 = OpConstant !0 42", "Error: Type Id is 0"},
@@ -886,7 +886,7 @@
          "Invalid image operand: 32770 has invalid mask component 32768"},
         {"OpSelectionMerge %1 !7",
          "Invalid selection control operand: 7 has invalid mask component 4"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/binary_to_text.literal_test.cpp b/test/binary_to_text.literal_test.cpp
index bcfb0f0..02daac7 100644
--- a/test/binary_to_text.literal_test.cpp
+++ b/test/binary_to_text.literal_test.cpp
@@ -32,7 +32,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StringLiterals, RoundTripLiteralsTest,
     ::testing::ValuesIn(std::vector<std::string>{
         "OpName %1 \"\"\n",           // empty
@@ -49,7 +49,7 @@
         "OpName %1 \"\\\"foo\nbar\\\"\"\n",       // escaped quote
         "OpName %1 \"\\\\foo\nbar\\\\\"\n",       // escaped backslash
         "OpName %1 \"\xE4\xBA\xB2\"\n",             // UTF-8
-    }),);
+    }));
 // clang-format on
 
 using RoundTripSpecialCaseLiteralsTest = spvtest::TextToBinaryTestBase<
@@ -63,13 +63,13 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StringLiterals, RoundTripSpecialCaseLiteralsTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
       {"OpName %1 \"\\foo\"\n", "OpName %1 \"foo\"\n"}, // Escape f
       {"OpName %1 \"\\\nfoo\"\n", "OpName %1 \"\nfoo\"\n"}, // Escape newline
       {"OpName %1 \"\\\xE4\xBA\xB2\"\n", "OpName %1 \"\xE4\xBA\xB2\"\n"}, // Escape utf-8
-    }),);
+    }));
 // clang-format on
 
 }  // namespace
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp
index 00ac86b..e8a02fd 100644
--- a/test/binary_to_text_test.cpp
+++ b/test/binary_to_text_test.cpp
@@ -171,7 +171,7 @@
               Eq(GetParam().expected_error_message));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidIds, BinaryToTextFail,
     ::testing::ValuesIn(std::vector<FailedDecodeCase>{
         {"", spvtest::MakeInstruction(SpvOpTypeVoid, {0}),
@@ -196,9 +196,9 @@
          "%2 = OpTypeVector %1 4",
          spvtest::MakeInstruction(SpvOpConstant, {2, 3, 999}),
          "Type Id 2 is not a scalar numeric type"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidIdsCheckedDuringLiteralCaseParsing, BinaryToTextFail,
     ::testing::ValuesIn(std::vector<FailedDecodeCase>{
         {"", spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}),
@@ -212,7 +212,7 @@
         {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 1.5",
          spvtest::MakeInstruction(SpvOpSwitch, {2, 3, 4, 5}),
          "Invalid OpSwitch: selector id 2 is not a scalar integer"},
-    }), );
+    }));
 
 TEST_F(TextToBinaryTest, OneInstruction) {
   const std::string input = "OpSource OpenCL_C 12\n";
@@ -243,7 +243,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     NumericLiterals, RoundTripInstructionsTest,
     // This test is independent of environment, so just test the one.
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -275,10 +275,10 @@
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0002p+1024\n", // NaN
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024\n", // Inf
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024\n", // -Inf
-            })), );
+            })));
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryAccessMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -292,9 +292,9 @@
                 "OpStore %1 %2 Volatile|Aligned 16\n",
                 "OpStore %1 %2 Volatile|Nontemporal\n",
                 "OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FPFastMathModeMasks, RoundTripInstructionsTest,
     Combine(
         ::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -310,9 +310,9 @@
             "OpDecorate %1 FPFastMathMode NotNaN|NotInf\n",
             "OpDecorate %1 FPFastMathMode NSZ|AllowRecip\n",
             "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n",
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2),
@@ -321,18 +321,18 @@
                 "OpLoopMerge %1 %2 Unroll\n",
                 "OpLoopMerge %1 %2 DontUnroll\n",
                 "OpLoopMerge %1 %2 Unroll|DontUnroll\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(LoopControlMasksV11, RoundTripInstructionsTest,
-                        Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_1,
-                                                  SPV_ENV_UNIVERSAL_1_2,
-                                                  SPV_ENV_UNIVERSAL_1_3),
-                                ::testing::ValuesIn(std::vector<std::string>{
-                                    "OpLoopMerge %1 %2 DependencyInfinite\n",
-                                    "OpLoopMerge %1 %2 DependencyLength 8\n",
-                                })), );
+INSTANTIATE_TEST_SUITE_P(LoopControlMasksV11, RoundTripInstructionsTest,
+                         Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_1,
+                                                   SPV_ENV_UNIVERSAL_1_2,
+                                                   SPV_ENV_UNIVERSAL_1_3),
+                                 ::testing::ValuesIn(std::vector<std::string>{
+                                     "OpLoopMerge %1 %2 DependencyInfinite\n",
+                                     "OpLoopMerge %1 %2 DependencyLength 8\n",
+                                 })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SelectionControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2),
@@ -341,9 +341,9 @@
                 "OpSelectionMerge %1 Flatten\n",
                 "OpSelectionMerge %1 DontFlatten\n",
                 "OpSelectionMerge %1 Flatten|DontFlatten\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -355,9 +355,9 @@
                 "%2 = OpFunction %1 Const %3\n",
                 "%2 = OpFunction %1 Inline|Pure|Const %3\n",
                 "%2 = OpFunction %1 DontInline|Const %3\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -378,9 +378,9 @@
                 "%2 = OpImageFetch %1 %3 %4 Sample|MinLod %5 %6\n",
                 "%2 = OpImageFetch %1 %3 %4"
                 " Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod"
-                " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"})), );
+                " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"})));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     NewInstructionsInSPIRV1_2, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
             ::testing::ValuesIn(std::vector<std::string>{
@@ -389,7 +389,7 @@
                 "OpExecutionModeId %1 LocalSizeHintId %2\n",
                 "OpDecorateId %1 AlignmentId %2\n",
                 "OpDecorateId %1 MaxByteOffsetId %2\n",
-            })), );
+            })));
 
 using MaskSorting = TextToBinaryTest;
 
@@ -537,23 +537,23 @@
   spvTextDestroy(decoded_text);
 }
 
-INSTANTIATE_TEST_CASE_P(GeneratorStrings, GeneratorStringTest,
-                        ::testing::ValuesIn(std::vector<GeneratorStringCase>{
-                            {SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"},
-                            {SPV_GENERATOR_LUNARG, 99, "LunarG; 99"},
-                            {SPV_GENERATOR_VALVE, 1, "Valve; 1"},
-                            {SPV_GENERATOR_CODEPLAY, 65535, "Codeplay; 65535"},
-                            {SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"},
-                            {SPV_GENERATOR_ARM, 1000, "ARM; 1000"},
-                            {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38,
-                             "Khronos LLVM/SPIR-V Translator; 38"},
-                            {SPV_GENERATOR_KHRONOS_ASSEMBLER, 2,
-                             "Khronos SPIR-V Tools Assembler; 2"},
-                            {SPV_GENERATOR_KHRONOS_GLSLANG, 1,
-                             "Khronos Glslang Reference Front End; 1"},
-                            {1000, 18, "Unknown(1000); 18"},
-                            {65535, 32767, "Unknown(65535); 32767"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(GeneratorStrings, GeneratorStringTest,
+                         ::testing::ValuesIn(std::vector<GeneratorStringCase>{
+                             {SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"},
+                             {SPV_GENERATOR_LUNARG, 99, "LunarG; 99"},
+                             {SPV_GENERATOR_VALVE, 1, "Valve; 1"},
+                             {SPV_GENERATOR_CODEPLAY, 65535, "Codeplay; 65535"},
+                             {SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"},
+                             {SPV_GENERATOR_ARM, 1000, "ARM; 1000"},
+                             {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38,
+                              "Khronos LLVM/SPIR-V Translator; 38"},
+                             {SPV_GENERATOR_KHRONOS_ASSEMBLER, 2,
+                              "Khronos SPIR-V Tools Assembler; 2"},
+                             {SPV_GENERATOR_KHRONOS_GLSLANG, 1,
+                              "Khronos Glslang Reference Front End; 1"},
+                             {1000, 18, "Unknown(1000); 18"},
+                             {65535, 32767, "Unknown(65535); 32767"},
+                         }));
 
 // TODO(dneto): Test new instructions and enums in SPIR-V 1.3
 
diff --git a/test/bit_stream.cpp b/test/bit_stream.cpp
deleted file mode 100644
index f02faf3..0000000
--- a/test/bit_stream.cpp
+++ /dev/null
@@ -1,1025 +0,0 @@
-// 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 <limits>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "source/comp/bit_stream.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-// Converts |buffer| to a stream of '0' and '1'.
-template <typename T>
-std::string BufferToStream(const std::vector<T>& buffer) {
-  std::stringstream ss;
-  for (auto it = buffer.begin(); it != buffer.end(); ++it) {
-    std::string str = std::bitset<sizeof(T) * 8>(*it).to_string();
-    // Strings generated by std::bitset::to_string are read right to left.
-    // Reversing to left to right.
-    std::reverse(str.begin(), str.end());
-    ss << str;
-  }
-  return ss.str();
-}
-
-// Converts a left-to-right input string of '0' and '1' to a buffer of |T|
-// words.
-template <typename T>
-std::vector<T> StreamToBuffer(std::string str) {
-  // The input string is left-to-right, the input argument of std::bitset needs
-  // to right-to-left. Instead of reversing tokens, reverse the entire string
-  // and iterate tokens from end to begin.
-  std::reverse(str.begin(), str.end());
-  const int word_size = static_cast<int>(sizeof(T) * 8);
-  const int str_length = static_cast<int>(str.length());
-  std::vector<T> buffer;
-  buffer.reserve(NumBitsToNumWords<sizeof(T)>(str.length()));
-  for (int index = str_length - word_size; index >= 0; index -= word_size) {
-    buffer.push_back(static_cast<T>(
-        std::bitset<sizeof(T) * 8>(str, index, word_size).to_ullong()));
-  }
-  const size_t suffix_length = str.length() % word_size;
-  if (suffix_length != 0) {
-    buffer.push_back(static_cast<T>(
-        std::bitset<sizeof(T) * 8>(str, 0, suffix_length).to_ullong()));
-  }
-  return buffer;
-}
-
-// Adds '0' chars at the end of the string until the size is a multiple of N.
-template <size_t N>
-std::string PadToWord(std::string&& str) {
-  const size_t tail_length = str.size() % N;
-  if (tail_length != 0) str += std::string(N - tail_length, '0');
-  return std::move(str);
-}
-
-// Adds '0' chars at the end of the string until the size is a multiple of N.
-template <size_t N>
-std::string PadToWord(const std::string& str) {
-  return PadToWord<N>(std::string(str));
-}
-
-// Converts a left-to-right stream of bits to std::bitset.
-template <size_t N>
-std::bitset<N> StreamToBitset(std::string str) {
-  std::reverse(str.begin(), str.end());
-  return std::bitset<N>(str);
-}
-
-// Converts a left-to-right stream of bits to uint64.
-uint64_t StreamToBits(std::string str) {
-  std::reverse(str.begin(), str.end());
-  return std::bitset<64>(str).to_ullong();
-}
-
-// A simple and inefficient implementatition of BitWriterInterface,
-// using std::stringstream. Intended for tests only.
-class BitWriterStringStream : public BitWriterInterface {
- public:
-  void WriteBits(uint64_t bits, size_t num_bits) override {
-    assert(num_bits <= 64);
-    ss_ << BitsToStream(bits, num_bits);
-  }
-
-  size_t GetNumBits() const override { return ss_.str().size(); }
-
-  std::vector<uint8_t> GetDataCopy() const override {
-    return StreamToBuffer<uint8_t>(ss_.str());
-  }
-
-  std::string GetStreamRaw() const { return ss_.str(); }
-
- private:
-  std::stringstream ss_;
-};
-
-// A simple and inefficient implementatition of BitReaderInterface.
-// Intended for tests only.
-class BitReaderFromString : public BitReaderInterface {
- public:
-  explicit BitReaderFromString(std::string&& str)
-      : str_(std::move(str)), pos_(0) {}
-
-  explicit BitReaderFromString(const std::vector<uint64_t>& buffer)
-      : str_(BufferToStream(buffer)), pos_(0) {}
-
-  explicit BitReaderFromString(const std::vector<uint8_t>& buffer)
-      : str_(PadToWord<64>(BufferToStream(buffer))), pos_(0) {}
-
-  size_t ReadBits(uint64_t* bits, size_t num_bits) override {
-    if (ReachedEnd()) return 0;
-    std::string sub = str_.substr(pos_, num_bits);
-    *bits = StreamToBits(sub);
-    pos_ += sub.length();
-    return sub.length();
-  }
-
-  size_t GetNumReadBits() const override { return pos_; }
-
-  bool ReachedEnd() const override { return pos_ >= str_.length(); }
-
- private:
-  std::string str_;
-  size_t pos_;
-};
-
-TEST(NumBitsToNumWords, Word8) {
-  EXPECT_EQ(0u, NumBitsToNumWords<8>(0));
-  EXPECT_EQ(1u, NumBitsToNumWords<8>(1));
-  EXPECT_EQ(1u, NumBitsToNumWords<8>(7));
-  EXPECT_EQ(1u, NumBitsToNumWords<8>(8));
-  EXPECT_EQ(2u, NumBitsToNumWords<8>(9));
-  EXPECT_EQ(2u, NumBitsToNumWords<8>(16));
-  EXPECT_EQ(3u, NumBitsToNumWords<8>(17));
-  EXPECT_EQ(3u, NumBitsToNumWords<8>(23));
-  EXPECT_EQ(3u, NumBitsToNumWords<8>(24));
-  EXPECT_EQ(4u, NumBitsToNumWords<8>(25));
-}
-
-TEST(NumBitsToNumWords, Word64) {
-  EXPECT_EQ(0u, NumBitsToNumWords<64>(0));
-  EXPECT_EQ(1u, NumBitsToNumWords<64>(1));
-  EXPECT_EQ(1u, NumBitsToNumWords<64>(64));
-  EXPECT_EQ(2u, NumBitsToNumWords<64>(65));
-  EXPECT_EQ(2u, NumBitsToNumWords<64>(128));
-  EXPECT_EQ(3u, NumBitsToNumWords<64>(129));
-}
-
-TEST(ZigZagCoding, Encode0) {
-  EXPECT_EQ(0u, EncodeZigZag(0, 0));
-  EXPECT_EQ(1u, EncodeZigZag(-1, 0));
-  EXPECT_EQ(2u, EncodeZigZag(1, 0));
-  EXPECT_EQ(3u, EncodeZigZag(-2, 0));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 1,
-            EncodeZigZag(std::numeric_limits<int64_t>::max(), 0));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
-            EncodeZigZag(std::numeric_limits<int64_t>::min(), 0));
-}
-
-TEST(ZigZagCoding, Decode0) {
-  EXPECT_EQ(0, DecodeZigZag(0, 0));
-  EXPECT_EQ(-1, DecodeZigZag(1, 0));
-  EXPECT_EQ(1, DecodeZigZag(2, 0));
-  EXPECT_EQ(-2, DecodeZigZag(3, 0));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max(), 0));
-  EXPECT_EQ(std::numeric_limits<int64_t>::max(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 1, 0));
-}
-
-TEST(ZigZagCoding, Encode1) {
-  EXPECT_EQ(0u, EncodeZigZag(0, 1));
-  EXPECT_EQ(1u, EncodeZigZag(1, 1));
-  EXPECT_EQ(2u, EncodeZigZag(-1, 1));
-  EXPECT_EQ(3u, EncodeZigZag(-2, 1));
-  EXPECT_EQ(4u, EncodeZigZag(2, 1));
-  EXPECT_EQ(5u, EncodeZigZag(3, 1));
-  EXPECT_EQ(6u, EncodeZigZag(-3, 1));
-  EXPECT_EQ(7u, EncodeZigZag(-4, 1));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 2,
-            EncodeZigZag(std::numeric_limits<int64_t>::max(), 1));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 1,
-            EncodeZigZag(std::numeric_limits<int64_t>::min() + 1, 1));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
-            EncodeZigZag(std::numeric_limits<int64_t>::min(), 1));
-}
-
-TEST(ZigZagCoding, Decode1) {
-  EXPECT_EQ(0, DecodeZigZag(0, 1));
-  EXPECT_EQ(1, DecodeZigZag(1, 1));
-  EXPECT_EQ(-1, DecodeZigZag(2, 1));
-  EXPECT_EQ(-2, DecodeZigZag(3, 1));
-  EXPECT_EQ(2, DecodeZigZag(4, 1));
-  EXPECT_EQ(3, DecodeZigZag(5, 1));
-  EXPECT_EQ(-3, DecodeZigZag(6, 1));
-  EXPECT_EQ(-4, DecodeZigZag(7, 1));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max(), 1));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min() + 1,
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 1, 1));
-  EXPECT_EQ(std::numeric_limits<int64_t>::max(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 2, 1));
-}
-
-TEST(ZigZagCoding, Encode2) {
-  EXPECT_EQ(0u, EncodeZigZag(0, 2));
-  EXPECT_EQ(1u, EncodeZigZag(1, 2));
-  EXPECT_EQ(2u, EncodeZigZag(2, 2));
-  EXPECT_EQ(3u, EncodeZigZag(3, 2));
-  EXPECT_EQ(4u, EncodeZigZag(-1, 2));
-  EXPECT_EQ(5u, EncodeZigZag(-2, 2));
-  EXPECT_EQ(6u, EncodeZigZag(-3, 2));
-  EXPECT_EQ(7u, EncodeZigZag(-4, 2));
-  EXPECT_EQ(8u, EncodeZigZag(4, 2));
-  EXPECT_EQ(9u, EncodeZigZag(5, 2));
-  EXPECT_EQ(10u, EncodeZigZag(6, 2));
-  EXPECT_EQ(11u, EncodeZigZag(7, 2));
-  EXPECT_EQ(12u, EncodeZigZag(-5, 2));
-  EXPECT_EQ(13u, EncodeZigZag(-6, 2));
-  EXPECT_EQ(14u, EncodeZigZag(-7, 2));
-  EXPECT_EQ(15u, EncodeZigZag(-8, 2));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 4,
-            EncodeZigZag(std::numeric_limits<int64_t>::max(), 2));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 3,
-            EncodeZigZag(std::numeric_limits<int64_t>::min() + 3, 2));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 2,
-            EncodeZigZag(std::numeric_limits<int64_t>::min() + 2, 2));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 1,
-            EncodeZigZag(std::numeric_limits<int64_t>::min() + 1, 2));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
-            EncodeZigZag(std::numeric_limits<int64_t>::min(), 2));
-}
-
-TEST(ZigZagCoding, Decode2) {
-  EXPECT_EQ(0, DecodeZigZag(0, 2));
-  EXPECT_EQ(1, DecodeZigZag(1, 2));
-  EXPECT_EQ(2, DecodeZigZag(2, 2));
-  EXPECT_EQ(3, DecodeZigZag(3, 2));
-  EXPECT_EQ(-1, DecodeZigZag(4, 2));
-  EXPECT_EQ(-2, DecodeZigZag(5, 2));
-  EXPECT_EQ(-3, DecodeZigZag(6, 2));
-  EXPECT_EQ(-4, DecodeZigZag(7, 2));
-  EXPECT_EQ(4, DecodeZigZag(8, 2));
-  EXPECT_EQ(5, DecodeZigZag(9, 2));
-  EXPECT_EQ(6, DecodeZigZag(10, 2));
-  EXPECT_EQ(7, DecodeZigZag(11, 2));
-  EXPECT_EQ(-5, DecodeZigZag(12, 2));
-  EXPECT_EQ(-6, DecodeZigZag(13, 2));
-  EXPECT_EQ(-7, DecodeZigZag(14, 2));
-  EXPECT_EQ(-8, DecodeZigZag(15, 2));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max(), 2));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min() + 1,
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 1, 2));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min() + 2,
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 2, 2));
-  EXPECT_EQ(std::numeric_limits<int64_t>::min() + 3,
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 3, 2));
-  EXPECT_EQ(std::numeric_limits<int64_t>::max(),
-            DecodeZigZag(std::numeric_limits<uint64_t>::max() - 4, 2));
-}
-
-TEST(ZigZagCoding, Encode63) {
-  EXPECT_EQ(0u, EncodeZigZag(0, 63));
-
-  for (int64_t i = 0; i < 0xFFFFFFFF; i += 1234567) {
-    const int64_t positive_val = GetLowerBits(i * i * i + i * i, 63) | 1UL;
-    ASSERT_EQ(static_cast<uint64_t>(positive_val),
-              EncodeZigZag(positive_val, 63));
-    ASSERT_EQ((1ULL << 63) - 1 + positive_val, EncodeZigZag(-positive_val, 63));
-  }
-
-  EXPECT_EQ((1ULL << 63) - 1,
-            EncodeZigZag(std::numeric_limits<int64_t>::max(), 63));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max() - 1,
-            EncodeZigZag(std::numeric_limits<int64_t>::min() + 1, 63));
-  EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
-            EncodeZigZag(std::numeric_limits<int64_t>::min(), 63));
-}
-
-TEST(BufToStream, UInt8_Empty) {
-  const std::string expected_bits = "";
-  std::vector<uint8_t> buffer = StreamToBuffer<uint8_t>(expected_bits);
-  EXPECT_TRUE(buffer.empty());
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(BufToStream, UInt8_OneWord) {
-  const std::string expected_bits = "00101100";
-  std::vector<uint8_t> buffer = StreamToBuffer<uint8_t>(expected_bits);
-  EXPECT_EQ(std::vector<uint8_t>({static_cast<uint8_t>(
-                StreamToBitset<8>(expected_bits).to_ulong())}),
-            buffer);
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(BufToStream, UInt8_MultipleWords) {
-  const std::string expected_bits =
-      "00100010"
-      "01101010"
-      "01111101"
-      "00100010";
-  std::vector<uint8_t> buffer = StreamToBuffer<uint8_t>(expected_bits);
-  EXPECT_EQ(std::vector<uint8_t>({
-                static_cast<uint8_t>(StreamToBitset<8>("00100010").to_ulong()),
-                static_cast<uint8_t>(StreamToBitset<8>("01101010").to_ulong()),
-                static_cast<uint8_t>(StreamToBitset<8>("01111101").to_ulong()),
-                static_cast<uint8_t>(StreamToBitset<8>("00100010").to_ulong()),
-            }),
-            buffer);
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(BufToStream, UInt64_Empty) {
-  const std::string expected_bits = "";
-  std::vector<uint64_t> buffer = StreamToBuffer<uint64_t>(expected_bits);
-  EXPECT_TRUE(buffer.empty());
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(BufToStream, UInt64_OneWord) {
-  const std::string expected_bits =
-      "0001000111101110011001101010101000100010110011000100010010001000";
-  std::vector<uint64_t> buffer = StreamToBuffer<uint64_t>(expected_bits);
-  ASSERT_EQ(1u, buffer.size());
-  EXPECT_EQ(0x1122334455667788u, buffer[0]);
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(BufToStream, UInt64_Unaligned) {
-  const std::string expected_bits =
-      "0010001001101010011111010010001001001010000111110010010010010101"
-      "0010001001101010011111111111111111111111";
-  std::vector<uint64_t> buffer = StreamToBuffer<uint64_t>(expected_bits);
-  EXPECT_EQ(std::vector<uint64_t>({
-                StreamToBits(expected_bits.substr(0, 64)),
-                StreamToBits(expected_bits.substr(64, 64)),
-            }),
-            buffer);
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(PadToWord<64>(expected_bits), result_bits);
-}
-
-TEST(BufToStream, UInt64_MultipleWords) {
-  const std::string expected_bits =
-      "0010001001101010011111010010001001001010000111110010010010010101"
-      "0010001001101010011111111111111111111111000111110010010010010111"
-      "0000000000000000000000000000000000000000000000000010010011111111";
-  std::vector<uint64_t> buffer = StreamToBuffer<uint64_t>(expected_bits);
-  EXPECT_EQ(std::vector<uint64_t>({
-                StreamToBits(expected_bits.substr(0, 64)),
-                StreamToBits(expected_bits.substr(64, 64)),
-                StreamToBits(expected_bits.substr(128, 64)),
-            }),
-            buffer);
-  const std::string result_bits = BufferToStream(buffer);
-  EXPECT_EQ(expected_bits, result_bits);
-}
-
-TEST(PadToWord, Test) {
-  EXPECT_EQ("10100000", PadToWord<8>("101"));
-  EXPECT_EQ(
-      "10100000"
-      "00000000",
-      PadToWord<16>("101"));
-  EXPECT_EQ(
-      "10100000"
-      "00000000"
-      "00000000"
-      "00000000",
-      PadToWord<32>("101"));
-  EXPECT_EQ(
-      "10100000"
-      "00000000"
-      "00000000"
-      "00000000"
-      "00000000"
-      "00000000"
-      "00000000"
-      "00000000",
-      PadToWord<64>("101"));
-}
-
-TEST(BitWriterStringStream, Empty) {
-  BitWriterStringStream writer;
-  EXPECT_EQ(0u, writer.GetNumBits());
-  EXPECT_EQ(0u, writer.GetDataSizeBytes());
-  EXPECT_EQ("", writer.GetStreamRaw());
-}
-
-TEST(BitWriterStringStream, WriteBits) {
-  BitWriterStringStream writer;
-  const uint64_t bits1 = 0x1 | 0x2 | 0x10;
-  writer.WriteBits(bits1, 5);
-  EXPECT_EQ(5u, writer.GetNumBits());
-  EXPECT_EQ(1u, writer.GetDataSizeBytes());
-  EXPECT_EQ("11001", writer.GetStreamRaw());
-}
-
-TEST(BitWriterStringStream, WriteUnencodedU8) {
-  BitWriterStringStream writer;
-  const uint8_t bits = 127;
-  writer.WriteUnencoded(bits);
-  EXPECT_EQ(8u, writer.GetNumBits());
-  EXPECT_EQ("11111110", writer.GetStreamRaw());
-}
-
-TEST(BitWriterStringStream, WriteUnencodedS64) {
-  BitWriterStringStream writer;
-  const int64_t bits = std::numeric_limits<int64_t>::min() + 7;
-  writer.WriteUnencoded(bits);
-  EXPECT_EQ(64u, writer.GetNumBits());
-  EXPECT_EQ("1110000000000000000000000000000000000000000000000000000000000001",
-            writer.GetStreamRaw());
-}
-
-TEST(BitWriterStringStream, WriteMultiple) {
-  BitWriterStringStream writer;
-
-  std::string expected_result;
-
-  const uint64_t b2_val = 0x4 | 0x2 | 0x40;
-  const std::string bits2 = BitsToStream(b2_val, 8);
-  writer.WriteBits(b2_val, 8);
-
-  const uint64_t val = 0x1 | 0x2 | 0x10;
-  const std::string bits3 = BitsToStream(val, 8);
-  writer.WriteBits(val, 8);
-
-  const std::string expected = bits2 + bits3;
-
-  EXPECT_EQ(expected.length(), writer.GetNumBits());
-  EXPECT_EQ(2u, writer.GetDataSizeBytes());
-  EXPECT_EQ(expected, writer.GetStreamRaw());
-
-  EXPECT_EQ(PadToWord<8>(expected), BufferToStream(writer.GetDataCopy()));
-}
-
-TEST(BitWriterWord64, Empty) {
-  BitWriterWord64 writer;
-  EXPECT_EQ(0u, writer.GetNumBits());
-  EXPECT_EQ(0u, writer.GetDataSizeBytes());
-}
-
-TEST(BitWriterWord64, WriteBits) {
-  BitWriterWord64 writer;
-  const uint64_t bits1 = 0x1 | 0x2 | 0x10;
-  writer.WriteBits(bits1, 5);
-  writer.WriteBits(bits1, 5);
-  writer.WriteBits(bits1, 5);
-  EXPECT_EQ(15u, writer.GetNumBits());
-  EXPECT_EQ(2u, writer.GetDataSizeBytes());
-}
-
-TEST(BitWriterWord64, WriteZeroBits) {
-  BitWriterWord64 writer;
-  writer.WriteBits(0, 0);
-  writer.WriteBits(1, 0);
-  EXPECT_EQ(0u, writer.GetNumBits());
-  writer.WriteBits(1, 1);
-  writer.WriteBits(0, 0);
-  writer.WriteBits(0, 63);
-  EXPECT_EQ(64u, writer.GetNumBits());
-  writer.WriteBits(0, 0);
-  writer.WriteBits(7, 3);
-  writer.WriteBits(0, 0);
-}
-
-TEST(BitWriterWord64, ComparisonTestWriteLotsOfBits) {
-  BitWriterStringStream writer1;
-  BitWriterWord64 writer2(16384);
-
-  for (uint64_t i = 0; i < 65000; i += 25) {
-    writer1.WriteBits(i, 16);
-    writer2.WriteBits(i, 16);
-    ASSERT_EQ(writer1.GetNumBits(), writer2.GetNumBits());
-  }
-}
-
-TEST(GetLowerBits, Test) {
-  EXPECT_EQ(0u, GetLowerBits<uint8_t>(255, 0));
-  EXPECT_EQ(1u, GetLowerBits<uint8_t>(255, 1));
-  EXPECT_EQ(3u, GetLowerBits<uint8_t>(255, 2));
-  EXPECT_EQ(7u, GetLowerBits<uint8_t>(255, 3));
-  EXPECT_EQ(15u, GetLowerBits<uint8_t>(255, 4));
-  EXPECT_EQ(31u, GetLowerBits<uint8_t>(255, 5));
-  EXPECT_EQ(63u, GetLowerBits<uint8_t>(255, 6));
-  EXPECT_EQ(127u, GetLowerBits<uint8_t>(255, 7));
-  EXPECT_EQ(255u, GetLowerBits<uint8_t>(255, 8));
-  EXPECT_EQ(0xFFu, GetLowerBits<uint32_t>(0xFFFFFFFF, 8));
-  EXPECT_EQ(0xFFFFu, GetLowerBits<uint32_t>(0xFFFFFFFF, 16));
-  EXPECT_EQ(0xFFFFFFu, GetLowerBits<uint32_t>(0xFFFFFFFF, 24));
-  EXPECT_EQ(0xFFFFFFu, GetLowerBits<uint64_t>(0xFFFFFFFFFFFF, 24));
-  EXPECT_EQ(0xFFFFFFFFFFFFFFFFu,
-            GetLowerBits<uint64_t>(0xFFFFFFFFFFFFFFFFu, 64));
-  EXPECT_EQ(StreamToBits("1010001110"),
-            GetLowerBits<uint64_t>(StreamToBits("1010001110111101111111"), 10));
-}
-
-TEST(BitReaderFromString, FromU8) {
-  std::vector<uint8_t> buffer = {
-      0xAA,
-      0xBB,
-      0xCC,
-      0xDD,
-  };
-
-  const std::string total_stream =
-      "01010101"
-      "11011101"
-      "00110011"
-      "10111011";
-
-  BitReaderFromString reader(buffer);
-
-  uint64_t bits = 0;
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(PadToWord<64>("01"), BitsToStream(bits));
-  EXPECT_EQ(20u, reader.ReadBits(&bits, 20));
-  EXPECT_EQ(PadToWord<64>("01010111011101001100"), BitsToStream(bits));
-  EXPECT_EQ(20u, reader.ReadBits(&bits, 20));
-  EXPECT_EQ(PadToWord<64>("11101110110000000000"), BitsToStream(bits));
-  EXPECT_EQ(22u, reader.ReadBits(&bits, 30));
-  EXPECT_EQ(PadToWord<64>("0000000000000000000000"), BitsToStream(bits));
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderFromString, FromU64) {
-  std::vector<uint64_t> buffer = {
-      0xAAAAAAAAAAAAAAAA,
-      0xBBBBBBBBBBBBBBBB,
-      0xCCCCCCCCCCCCCCCC,
-      0xDDDDDDDDDDDDDDDD,
-  };
-
-  const std::string total_stream =
-      "0101010101010101010101010101010101010101010101010101010101010101"
-      "1101110111011101110111011101110111011101110111011101110111011101"
-      "0011001100110011001100110011001100110011001100110011001100110011"
-      "1011101110111011101110111011101110111011101110111011101110111011";
-
-  BitReaderFromString reader(buffer);
-
-  uint64_t bits = 0;
-  size_t pos = 0;
-  size_t to_read = 5;
-  while (reader.ReadBits(&bits, to_read) > 0) {
-    EXPECT_EQ(BitsToStream(bits),
-              PadToWord<64>(total_stream.substr(pos, to_read)));
-    pos += to_read;
-    to_read = (to_read + 35) % 64 + 1;
-  }
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderWord64, ReadBitsSingleByte) {
-  BitReaderWord64 reader(std::vector<uint8_t>({uint8_t(0xF0)}));
-  EXPECT_FALSE(reader.ReachedEnd());
-
-  uint64_t bits = 0;
-  EXPECT_EQ(1u, reader.ReadBits(&bits, 1));
-  EXPECT_EQ(0u, bits);
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(0u, bits);
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(2u, bits);
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(3u, bits);
-  EXPECT_FALSE(reader.OnlyZeroesLeft());
-  EXPECT_FALSE(reader.ReachedEnd());
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(1u, bits);
-  EXPECT_TRUE(reader.OnlyZeroesLeft());
-  EXPECT_FALSE(reader.ReachedEnd());
-  EXPECT_EQ(55u, reader.ReadBits(&bits, 64));
-  EXPECT_EQ(0u, bits);
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderWord64, ReadBitsTwoWords) {
-  std::vector<uint64_t> buffer = {0x0000000000000001, 0x0000000000FFFFFF};
-
-  BitReaderWord64 reader(std::move(buffer));
-
-  uint64_t bits = 0;
-  EXPECT_EQ(1u, reader.ReadBits(&bits, 1));
-  EXPECT_EQ(1u, bits);
-  EXPECT_EQ(62u, reader.ReadBits(&bits, 62));
-  EXPECT_EQ(0u, bits);
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(2u, bits);
-  EXPECT_EQ(3u, reader.ReadBits(&bits, 3));
-  EXPECT_EQ(7u, bits);
-  EXPECT_FALSE(reader.OnlyZeroesLeft());
-  EXPECT_EQ(32u, reader.ReadBits(&bits, 32));
-  EXPECT_EQ(0xFFFFFu, bits);
-  EXPECT_TRUE(reader.OnlyZeroesLeft());
-  EXPECT_FALSE(reader.ReachedEnd());
-  EXPECT_EQ(28u, reader.ReadBits(&bits, 32));
-  EXPECT_EQ(0u, bits);
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderFromString, ReadUnencodedU8) {
-  BitReaderFromString reader("11111110");
-  uint8_t val = 0;
-  ASSERT_TRUE(reader.ReadUnencoded(&val));
-  EXPECT_EQ(8u, reader.GetNumReadBits());
-  EXPECT_EQ(127, val);
-}
-
-TEST(BitReaderFromString, ReadUnencodedU16Fail) {
-  BitReaderFromString reader("11111110");
-  uint16_t val = 0;
-  ASSERT_FALSE(reader.ReadUnencoded(&val));
-}
-
-TEST(BitReaderFromString, ReadUnencodedS64) {
-  BitReaderFromString reader(
-      "1110000000000000000000000000000000000000000000000000000000000001");
-  int64_t val = 0;
-  ASSERT_TRUE(reader.ReadUnencoded(&val));
-  EXPECT_EQ(64u, reader.GetNumReadBits());
-  EXPECT_EQ(std::numeric_limits<int64_t>::min() + 7, val);
-}
-
-TEST(BitReaderWord64, FromU8) {
-  std::vector<uint8_t> buffer = {
-      0xAA,
-      0xBB,
-      0xCC,
-      0xDD,
-  };
-
-  BitReaderWord64 reader(std::move(buffer));
-
-  uint64_t bits = 0;
-  EXPECT_EQ(2u, reader.ReadBits(&bits, 2));
-  EXPECT_EQ(PadToWord<64>("01"), BitsToStream(bits));
-  EXPECT_EQ(20u, reader.ReadBits(&bits, 20));
-  EXPECT_EQ(PadToWord<64>("01010111011101001100"), BitsToStream(bits));
-  EXPECT_EQ(20u, reader.ReadBits(&bits, 20));
-  EXPECT_EQ(PadToWord<64>("11101110110000000000"), BitsToStream(bits));
-  EXPECT_EQ(22u, reader.ReadBits(&bits, 30));
-  EXPECT_EQ(PadToWord<64>("0000000000000000000000"), BitsToStream(bits));
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderWord64, FromU64) {
-  std::vector<uint64_t> buffer = {
-      0xAAAAAAAAAAAAAAAA,
-      0xBBBBBBBBBBBBBBBB,
-      0xCCCCCCCCCCCCCCCC,
-      0xDDDDDDDDDDDDDDDD,
-  };
-
-  const std::string total_stream =
-      "0101010101010101010101010101010101010101010101010101010101010101"
-      "1101110111011101110111011101110111011101110111011101110111011101"
-      "0011001100110011001100110011001100110011001100110011001100110011"
-      "1011101110111011101110111011101110111011101110111011101110111011";
-
-  BitReaderWord64 reader(std::move(buffer));
-
-  uint64_t bits = 0;
-  size_t pos = 0;
-  size_t to_read = 5;
-  while (reader.ReadBits(&bits, to_read) > 0) {
-    EXPECT_EQ(BitsToStream(bits),
-              PadToWord<64>(total_stream.substr(pos, to_read)));
-    pos += to_read;
-    to_read = (to_read + 35) % 64 + 1;
-  }
-  EXPECT_TRUE(reader.ReachedEnd());
-}
-
-TEST(BitReaderWord64, ComparisonLotsOfU8) {
-  std::vector<uint8_t> buffer;
-  for (uint32_t i = 0; i < 10003; ++i) {
-    buffer.push_back(static_cast<uint8_t>(i % 255));
-  }
-
-  BitReaderFromString reader1(buffer);
-  BitReaderWord64 reader2(std::move(buffer));
-
-  uint64_t bits1 = 0, bits2 = 0;
-  size_t to_read = 5;
-  while (reader1.ReadBits(&bits1, to_read) > 0) {
-    reader2.ReadBits(&bits2, to_read);
-    EXPECT_EQ(bits1, bits2);
-    to_read = (to_read + 35) % 64 + 1;
-  }
-
-  EXPECT_EQ(0u, reader2.ReadBits(&bits2, 1));
-}
-
-TEST(BitReaderWord64, ComparisonLotsOfU64) {
-  std::vector<uint64_t> buffer;
-  for (uint64_t i = 0; i < 1000; ++i) {
-    buffer.push_back(i);
-  }
-
-  BitReaderFromString reader1(buffer);
-  BitReaderWord64 reader2(std::move(buffer));
-
-  uint64_t bits1 = 0, bits2 = 0;
-  size_t to_read = 5;
-  while (reader1.ReadBits(&bits1, to_read) > 0) {
-    reader2.ReadBits(&bits2, to_read);
-    EXPECT_EQ(bits1, bits2);
-    to_read = (to_read + 35) % 64 + 1;
-  }
-
-  EXPECT_EQ(0u, reader2.ReadBits(&bits2, 1));
-}
-
-TEST(ReadWriteWord64, ReadWriteLotsOfBits) {
-  BitWriterWord64 writer(16384);
-  for (uint64_t i = 0; i < 65000; i += 25) {
-    const uint64_t num_bits = i % 64 + 1;
-    const uint64_t bits = i >> (64 - num_bits);
-    writer.WriteBits(bits, size_t(num_bits));
-  }
-
-  BitReaderWord64 reader(writer.GetDataCopy());
-  for (uint64_t i = 0; i < 65000; i += 25) {
-    const uint64_t num_bits = i % 64 + 1;
-    const uint64_t expected_bits = i >> (64 - num_bits);
-    uint64_t bits = 0;
-    reader.ReadBits(&bits, size_t(num_bits));
-    EXPECT_EQ(expected_bits, bits);
-  }
-
-  EXPECT_TRUE(reader.OnlyZeroesLeft());
-}
-
-TEST(VariableWidthWrite, Write0U) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU64(0, 2);
-  EXPECT_EQ("000", writer.GetStreamRaw());
-  writer.WriteVariableWidthU32(0, 2);
-  EXPECT_EQ(
-      "000"
-      "000",
-      writer.GetStreamRaw());
-  writer.WriteVariableWidthU16(0, 2);
-  EXPECT_EQ(
-      "000"
-      "000"
-      "000",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, WriteSmallUnsigned) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU64(1, 2);
-  EXPECT_EQ("100", writer.GetStreamRaw());
-  writer.WriteVariableWidthU32(2, 2);
-  EXPECT_EQ(
-      "100"
-      "010",
-      writer.GetStreamRaw());
-  writer.WriteVariableWidthU16(3, 2);
-  EXPECT_EQ(
-      "100"
-      "010"
-      "110",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, WriteSmallSigned) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthS64(1, 2, 0);
-  EXPECT_EQ("010", writer.GetStreamRaw());
-  writer.WriteVariableWidthS64(-1, 2, 0);
-  EXPECT_EQ(
-      "010"
-      "100",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, U64Val127ChunkLength7) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU64(127, 7);
-  EXPECT_EQ(
-      "1111111"
-      "0",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, U32Val255ChunkLength7) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU32(255, 7);
-  EXPECT_EQ(
-      "1111111"
-      "1"
-      "1000000"
-      "0",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, U16Val2ChunkLength4) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU16(2, 4);
-  EXPECT_EQ(
-      "0100"
-      "0",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthWrite, U64ValAAAAChunkLength2) {
-  BitWriterStringStream writer;
-  writer.WriteVariableWidthU64(0xAAAA, 2);
-  EXPECT_EQ(
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "0",
-      writer.GetStreamRaw());
-}
-
-TEST(VariableWidthRead, U64Val127ChunkLength7) {
-  BitReaderFromString reader(
-      "1111111"
-      "0");
-  uint64_t val = 0;
-  ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 7));
-  EXPECT_EQ(127u, val);
-}
-
-TEST(VariableWidthRead, U32Val255ChunkLength7) {
-  BitReaderFromString reader(
-      "1111111"
-      "1"
-      "1000000"
-      "0");
-  uint32_t val = 0;
-  ASSERT_TRUE(reader.ReadVariableWidthU32(&val, 7));
-  EXPECT_EQ(255u, val);
-}
-
-TEST(VariableWidthRead, U16Val2ChunkLength4) {
-  BitReaderFromString reader(
-      "0100"
-      "0");
-  uint16_t val = 0;
-  ASSERT_TRUE(reader.ReadVariableWidthU16(&val, 4));
-  EXPECT_EQ(2u, val);
-}
-
-TEST(VariableWidthRead, U64ValAAAAChunkLength2) {
-  BitReaderFromString reader(
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "1"
-      "01"
-      "0");
-  uint64_t val = 0;
-  ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 2));
-  EXPECT_EQ(0xAAAAu, val);
-}
-
-TEST(VariableWidthRead, FailTooShort) {
-  BitReaderFromString reader("00000001100000");
-  uint64_t val = 0;
-  ASSERT_FALSE(reader.ReadVariableWidthU64(&val, 7));
-}
-
-TEST(VariableWidthWriteRead, SingleWriteReadU64) {
-  for (uint64_t i = 0; i < 1000000; i += 1234) {
-    const uint64_t val = i * i * i;
-    const size_t chunk_length = size_t(i % 16 + 1);
-
-    BitWriterWord64 writer;
-    writer.WriteVariableWidthU64(val, chunk_length);
-
-    BitReaderWord64 reader(writer.GetDataCopy());
-    uint64_t read_val = 0;
-    ASSERT_TRUE(reader.ReadVariableWidthU64(&read_val, chunk_length));
-
-    ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length;
-  }
-}
-
-TEST(VariableWidthWriteRead, SingleWriteReadS64) {
-  for (int64_t i = 0; i < 1000000; i += 4321) {
-    const int64_t val = i * i * (i % 2 ? -i : i);
-    const size_t chunk_length = size_t(i % 16 + 1);
-    const size_t zigzag_exponent = size_t(i % 13);
-
-    BitWriterWord64 writer;
-    writer.WriteVariableWidthS64(val, chunk_length, zigzag_exponent);
-
-    BitReaderWord64 reader(writer.GetDataCopy());
-    int64_t read_val = 0;
-    ASSERT_TRUE(
-        reader.ReadVariableWidthS64(&read_val, chunk_length, zigzag_exponent));
-
-    ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length;
-  }
-}
-
-TEST(VariableWidthWriteRead, SingleWriteReadU32) {
-  for (uint32_t i = 0; i < 100000; i += 123) {
-    const uint32_t val = i * i;
-    const size_t chunk_length = i % 16 + 1;
-
-    BitWriterWord64 writer;
-    writer.WriteVariableWidthU32(val, chunk_length);
-
-    BitReaderWord64 reader(writer.GetDataCopy());
-    uint32_t read_val = 0;
-    ASSERT_TRUE(reader.ReadVariableWidthU32(&read_val, chunk_length));
-
-    ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length;
-  }
-}
-
-TEST(VariableWidthWriteRead, SingleWriteReadU16) {
-  for (int i = 0; i < 65536; i += 123) {
-    const uint16_t val = static_cast<int16_t>(i);
-    const size_t chunk_length = val % 10 + 1;
-
-    BitWriterWord64 writer;
-    writer.WriteVariableWidthU16(val, chunk_length);
-
-    BitReaderWord64 reader(writer.GetDataCopy());
-    uint16_t read_val = 0;
-    ASSERT_TRUE(reader.ReadVariableWidthU16(&read_val, chunk_length));
-
-    ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length;
-  }
-}
-
-TEST(VariableWidthWriteRead, SmallNumbersChunkLength4) {
-  const std::vector<uint64_t> expected_values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-
-  BitWriterWord64 writer;
-  for (uint64_t val : expected_values) {
-    writer.WriteVariableWidthU64(val, 4);
-  }
-
-  EXPECT_EQ(50u, writer.GetNumBits());
-
-  std::vector<uint64_t> actual_values;
-  BitReaderWord64 reader(writer.GetDataCopy());
-  while (!reader.OnlyZeroesLeft()) {
-    uint64_t val = 0;
-    ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 4));
-    actual_values.push_back(val);
-  }
-
-  EXPECT_EQ(expected_values, actual_values);
-}
-
-TEST(VariableWidthWriteRead, VariedNumbersChunkLength8) {
-  const std::vector<uint64_t> expected_values = {1000, 0, 255, 4294967296};
-  const size_t kExpectedNumBits = 9 * (2 + 1 + 1 + 5);
-
-  BitWriterWord64 writer;
-  for (uint64_t val : expected_values) {
-    writer.WriteVariableWidthU64(val, 8);
-  }
-
-  EXPECT_EQ(kExpectedNumBits, writer.GetNumBits());
-
-  std::vector<uint64_t> actual_values;
-  BitReaderWord64 reader(writer.GetDataCopy());
-  while (!reader.OnlyZeroesLeft()) {
-    uint64_t val = 0;
-    ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 8));
-    actual_values.push_back(val);
-  }
-
-  EXPECT_EQ(expected_values, actual_values);
-}
-
-}  // namespace
-}  // namespace comp
-}  // namespace spvtools
diff --git a/test/comp/CMakeLists.txt b/test/comp/CMakeLists.txt
deleted file mode 100644
index c947fde..0000000
--- a/test/comp/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-
-set(VAL_TEST_COMMON_SRCS
-  ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h
-  ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
-)
-
-if(SPIRV_BUILD_COMPRESSION)
-  add_spvtools_unittest(TARGET markv_codec
-    SRCS
-      markv_codec_test.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_factory.cpp
-      ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_shader.cpp
-      ${VAL_TEST_COMMON_SRCS}
-    LIBS SPIRV-Tools-comp ${SPIRV_TOOLS}
-  )
-endif(SPIRV_BUILD_COMPRESSION)
diff --git a/test/comp/markv_codec_test.cpp b/test/comp/markv_codec_test.cpp
deleted file mode 100644
index 283fcd3..0000000
--- a/test/comp/markv_codec_test.cpp
+++ /dev/null
@@ -1,829 +0,0 @@
-// 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.
-
-// Tests for unique type declaration rules validator.
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "source/comp/markv.h"
-#include "test/test_fixture.h"
-#include "test/unit_spirv.h"
-#include "tools/comp/markv_model_factory.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-using spvtest::ScopedContext;
-using MarkvTest = ::testing::TestWithParam<MarkvModelType>;
-
-void DiagnosticsMessageHandler(spv_message_level_t level, const char*,
-                               const spv_position_t& position,
-                               const char* message) {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: " << position.index << ": " << message << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cout << "warning: " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cout << "info: " << position.index << ": " << message << std::endl;
-      break;
-    default:
-      break;
-  }
-}
-
-// Compiles |code| to SPIR-V |words|.
-void Compile(const std::string& code, std::vector<uint32_t>* words,
-             uint32_t options = SPV_TEXT_TO_BINARY_OPTION_NONE,
-             spv_target_env env = SPV_ENV_UNIVERSAL_1_2) {
-  spvtools::Context ctx(env);
-  ctx.SetMessageConsumer(DiagnosticsMessageHandler);
-
-  spv_binary spirv_binary;
-  ASSERT_EQ(SPV_SUCCESS, spvTextToBinaryWithOptions(
-                             ctx.CContext(), code.c_str(), code.size(), options,
-                             &spirv_binary, nullptr));
-
-  *words = std::vector<uint32_t>(spirv_binary->code,
-                                 spirv_binary->code + spirv_binary->wordCount);
-
-  spvBinaryDestroy(spirv_binary);
-}
-
-// Disassembles SPIR-V |words| to |out_text|.
-void Disassemble(const std::vector<uint32_t>& words, std::string* out_text,
-                 spv_target_env env = SPV_ENV_UNIVERSAL_1_2) {
-  spvtools::Context ctx(env);
-  ctx.SetMessageConsumer(DiagnosticsMessageHandler);
-
-  spv_text text = nullptr;
-  ASSERT_EQ(SPV_SUCCESS, spvBinaryToText(ctx.CContext(), words.data(),
-                                         words.size(), 0, &text, nullptr));
-  assert(text);
-
-  *out_text = std::string(text->str, text->length);
-  spvTextDestroy(text);
-}
-
-// Encodes/decodes |original|, assembles/dissasembles |original|, then compares
-// the results of the two operations.
-void TestEncodeDecode(MarkvModelType model_type,
-                      const std::string& original_text) {
-  spvtools::Context ctx(SPV_ENV_UNIVERSAL_1_2);
-  std::unique_ptr<MarkvModel> model = CreateMarkvModel(model_type);
-  MarkvCodecOptions options;
-
-  std::vector<uint32_t> expected_binary;
-  Compile(original_text, &expected_binary);
-  ASSERT_FALSE(expected_binary.empty());
-
-  std::string expected_text;
-  Disassemble(expected_binary, &expected_text);
-  ASSERT_FALSE(expected_text.empty());
-
-  std::vector<uint32_t> binary_to_encode;
-  Compile(original_text, &binary_to_encode,
-          SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  ASSERT_FALSE(binary_to_encode.empty());
-
-  std::stringstream encoder_comments;
-  const auto output_to_string_stream =
-      [&encoder_comments](const std::string& str) { encoder_comments << str; };
-
-  std::vector<uint8_t> markv;
-  ASSERT_EQ(SPV_SUCCESS,
-            SpirvToMarkv(ctx.CContext(), binary_to_encode, options, *model,
-                         DiagnosticsMessageHandler, output_to_string_stream,
-                         MarkvDebugConsumer(), &markv));
-  ASSERT_FALSE(markv.empty());
-
-  std::vector<uint32_t> decoded_binary;
-  ASSERT_EQ(SPV_SUCCESS,
-            MarkvToSpirv(ctx.CContext(), markv, options, *model,
-                         DiagnosticsMessageHandler, MarkvLogConsumer(),
-                         MarkvDebugConsumer(), &decoded_binary));
-  ASSERT_FALSE(decoded_binary.empty());
-
-  EXPECT_EQ(expected_binary, decoded_binary) << encoder_comments.str();
-
-  std::string decoded_text;
-  Disassemble(decoded_binary, &decoded_text);
-  ASSERT_FALSE(decoded_text.empty());
-
-  EXPECT_EQ(expected_text, decoded_text) << encoder_comments.str();
-}
-
-void TestEncodeDecodeShaderMainBody(MarkvModelType model_type,
-                                    const std::string& body) {
-  const std::string prefix =
-      R"(
-OpCapability Shader
-OpCapability Int64
-OpCapability Float64
-%ext_inst = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-%void = OpTypeVoid
-%func = OpTypeFunction %void
-%bool = OpTypeBool
-%f32 = OpTypeFloat 32
-%u32 = OpTypeInt 32 0
-%s32 = OpTypeInt 32 1
-%f64 = OpTypeFloat 64
-%u64 = OpTypeInt 64 0
-%s64 = OpTypeInt 64 1
-%boolvec2 = OpTypeVector %bool 2
-%s32vec2 = OpTypeVector %s32 2
-%u32vec2 = OpTypeVector %u32 2
-%f32vec2 = OpTypeVector %f32 2
-%f64vec2 = OpTypeVector %f64 2
-%boolvec3 = OpTypeVector %bool 3
-%u32vec3 = OpTypeVector %u32 3
-%s32vec3 = OpTypeVector %s32 3
-%f32vec3 = OpTypeVector %f32 3
-%f64vec3 = OpTypeVector %f64 3
-%boolvec4 = OpTypeVector %bool 4
-%u32vec4 = OpTypeVector %u32 4
-%s32vec4 = OpTypeVector %s32 4
-%f32vec4 = OpTypeVector %f32 4
-%f64vec4 = OpTypeVector %f64 4
-
-%f32_0 = OpConstant %f32 0
-%f32_1 = OpConstant %f32 1
-%f32_2 = OpConstant %f32 2
-%f32_3 = OpConstant %f32 3
-%f32_4 = OpConstant %f32 4
-%f32_pi = OpConstant %f32 3.14159
-
-%s32_0 = OpConstant %s32 0
-%s32_1 = OpConstant %s32 1
-%s32_2 = OpConstant %s32 2
-%s32_3 = OpConstant %s32 3
-%s32_4 = OpConstant %s32 4
-%s32_m1 = OpConstant %s32 -1
-
-%u32_0 = OpConstant %u32 0
-%u32_1 = OpConstant %u32 1
-%u32_2 = OpConstant %u32 2
-%u32_3 = OpConstant %u32 3
-%u32_4 = OpConstant %u32 4
-
-%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
-%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
-%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
-%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3
-%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
-%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4
-
-%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
-%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
-%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2
-%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3
-%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
-%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4
-
-%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
-%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
-%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
-%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
-%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
-%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
-
-%main = OpFunction %void None %func
-%main_entry = OpLabel)";
-
-  const std::string suffix =
-      R"(
-OpReturn
-OpFunctionEnd)";
-
-  TestEncodeDecode(model_type, prefix + body + suffix);
-}
-
-TEST_P(MarkvTest, U32Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-%u32 = OpTypeInt 32 0
-%100 = OpConstant %u32 0
-%200 = OpConstant %u32 1
-%300 = OpConstant %u32 4294967295
-)");
-}
-
-TEST_P(MarkvTest, S32Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-%s32 = OpTypeInt 32 1
-%100 = OpConstant %s32 0
-%200 = OpConstant %s32 1
-%300 = OpConstant %s32 -1
-%400 = OpConstant %s32 2147483647
-%500 = OpConstant %s32 -2147483648
-)");
-}
-
-TEST_P(MarkvTest, U64Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Int64
-OpMemoryModel Logical GLSL450
-%u64 = OpTypeInt 64 0
-%100 = OpConstant %u64 0
-%200 = OpConstant %u64 1
-%300 = OpConstant %u64 18446744073709551615
-)");
-}
-
-TEST_P(MarkvTest, S64Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Int64
-OpMemoryModel Logical GLSL450
-%s64 = OpTypeInt 64 1
-%100 = OpConstant %s64 0
-%200 = OpConstant %s64 1
-%300 = OpConstant %s64 -1
-%400 = OpConstant %s64 9223372036854775807
-%500 = OpConstant %s64 -9223372036854775808
-)");
-}
-
-TEST_P(MarkvTest, U16Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Int16
-OpMemoryModel Logical GLSL450
-%u16 = OpTypeInt 16 0
-%100 = OpConstant %u16 0
-%200 = OpConstant %u16 1
-%300 = OpConstant %u16 65535
-)");
-}
-
-TEST_P(MarkvTest, S16Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Int16
-OpMemoryModel Logical GLSL450
-%s16 = OpTypeInt 16 1
-%100 = OpConstant %s16 0
-%200 = OpConstant %s16 1
-%300 = OpConstant %s16 -1
-%400 = OpConstant %s16 32767
-%500 = OpConstant %s16 -32768
-)");
-}
-
-TEST_P(MarkvTest, F32Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-%f32 = OpTypeFloat 32
-%100 = OpConstant %f32 0
-%200 = OpConstant %f32 1
-%300 = OpConstant %f32 0.1
-%400 = OpConstant %f32 -0.1
-)");
-}
-
-TEST_P(MarkvTest, F64Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Float64
-OpMemoryModel Logical GLSL450
-%f64 = OpTypeFloat 64
-%100 = OpConstant %f64 0
-%200 = OpConstant %f64 1
-%300 = OpConstant %f64 0.1
-%400 = OpConstant %f64 -0.1
-)");
-}
-
-TEST_P(MarkvTest, F16Literal) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Float16
-OpMemoryModel Logical GLSL450
-%f16 = OpTypeFloat 16
-%100 = OpConstant %f16 0
-%200 = OpConstant %f16 1
-%300 = OpConstant %f16 0.1
-%400 = OpConstant %f16 -0.1
-)");
-}
-
-TEST_P(MarkvTest, StringLiteral) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpExtension "SPV_KHR_16bit_storage"
-OpExtension "xxx"
-OpExtension "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
-OpExtension ""
-OpMemoryModel Logical GLSL450
-)");
-}
-
-TEST_P(MarkvTest, WithFunction) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpExtension "SPV_KHR_16bit_storage"
-OpMemoryModel Physical32 OpenCL
-%f32 = OpTypeFloat 32
-%u32 = OpTypeInt 32 0
-%void = OpTypeVoid
-%void_func = OpTypeFunction %void
-%100 = OpConstant %u32 1
-%200 = OpConstant %u32 2
-%main = OpFunction %void None %void_func
-%entry_main = OpLabel
-%300 = OpIAdd %u32 %100 %200
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, WithMultipleFunctions) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-%f32 = OpTypeFloat 32
-%one = OpConstant %f32 1
-%void = OpTypeVoid
-%void_func = OpTypeFunction %void
-%f32_func = OpTypeFunction %f32 %f32
-%sqr_plus_one = OpFunction %f32 None %f32_func
-%x = OpFunctionParameter %f32
-%100 = OpLabel
-%x2 = OpFMul %f32 %x %x
-%x2p1 = OpFunctionCall %f32 %plus_one %x2
-OpReturnValue %x2p1
-OpFunctionEnd
-%plus_one = OpFunction %f32 None %f32_func
-%y = OpFunctionParameter %f32
-%200 = OpLabel
-%yp1 = OpFAdd %f32 %y %one
-OpReturnValue %yp1
-OpFunctionEnd
-%main = OpFunction %void None %void_func
-%entry_main = OpLabel
-%1p1 = OpFunctionCall %f32 %sqr_plus_one %one
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, ForwardDeclaredId) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-OpEntryPoint Kernel %1 "simple_kernel"
-%2 = OpTypeInt 32 0
-%3 = OpTypeVector %2 2
-%4 = OpConstant %2 2
-%5 = OpTypeArray %2 %4
-%6 = OpTypeVoid
-%7 = OpTypeFunction %6
-%1 = OpFunction %6 None %7
-%8 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, WithSwitch) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpCapability Int64
-OpMemoryModel Physical32 OpenCL
-%u64 = OpTypeInt 64 0
-%void = OpTypeVoid
-%void_func = OpTypeFunction %void
-%val = OpConstant %u64 1
-%main = OpFunction %void None %void_func
-%entry_main = OpLabel
-OpSwitch %val %default 1 %case1 1000000000000 %case2
-%case1 = OpLabel
-OpNop
-OpBranch %after_switch
-%case2 = OpLabel
-OpNop
-OpBranch %after_switch
-%default = OpLabel
-OpNop
-OpBranch %after_switch
-%after_switch = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, WithLoop) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-%void = OpTypeVoid
-%void_func = OpTypeFunction %void
-%main = OpFunction %void None %void_func
-%entry_main = OpLabel
-OpLoopMerge %merge %continue DontUnroll|DependencyLength 10
-OpBranch %begin_loop
-%begin_loop = OpLabel
-OpNop
-OpBranch %continue
-%continue = OpLabel
-OpNop
-OpBranch %begin_loop
-%merge = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, WithDecorate) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-OpDecorate %1 ArrayStride 4
-OpDecorate %1 Uniform
-%2 = OpTypeFloat 32
-%1 = OpTypeRuntimeArray %2
-)");
-}
-
-TEST_P(MarkvTest, WithExtInst) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-%opencl = OpExtInstImport "OpenCL.std"
-OpMemoryModel Physical32 OpenCL
-%f32 = OpTypeFloat 32
-%void = OpTypeVoid
-%void_func = OpTypeFunction %void
-%100 = OpConstant %f32 1.1
-%main = OpFunction %void None %void_func
-%entry_main = OpLabel
-%200 = OpExtInst %f32 %opencl cos %100
-OpReturn
-OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, F32Mul) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpFMul %f32 %f32_0 %f32_1
-%val2 = OpFMul %f32 %f32_2 %f32_0
-%val3 = OpFMul %f32 %f32_pi %f32_2
-%val4 = OpFMul %f32 %f32_1 %f32_1
-)");
-}
-
-TEST_P(MarkvTest, U32Mul) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpIMul %u32 %u32_0 %u32_1
-%val2 = OpIMul %u32 %u32_2 %u32_0
-%val3 = OpIMul %u32 %u32_3 %u32_2
-%val4 = OpIMul %u32 %u32_1 %u32_1
-)");
-}
-
-TEST_P(MarkvTest, S32Mul) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpIMul %s32 %s32_0 %s32_1
-%val2 = OpIMul %s32 %s32_2 %s32_0
-%val3 = OpIMul %s32 %s32_m1 %s32_2
-%val4 = OpIMul %s32 %s32_1 %s32_1
-)");
-}
-
-TEST_P(MarkvTest, F32Add) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpFAdd %f32 %f32_0 %f32_1
-%val2 = OpFAdd %f32 %f32_2 %f32_0
-%val3 = OpFAdd %f32 %f32_pi %f32_2
-%val4 = OpFAdd %f32 %f32_1 %f32_1
-)");
-}
-
-TEST_P(MarkvTest, U32Add) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpIAdd %u32 %u32_0 %u32_1
-%val2 = OpIAdd %u32 %u32_2 %u32_0
-%val3 = OpIAdd %u32 %u32_3 %u32_2
-%val4 = OpIAdd %u32 %u32_1 %u32_1
-)");
-}
-
-TEST_P(MarkvTest, S32Add) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%val1 = OpIAdd %s32 %s32_0 %s32_1
-%val2 = OpIAdd %s32 %s32_2 %s32_0
-%val3 = OpIAdd %s32 %s32_m1 %s32_2
-%val4 = OpIAdd %s32 %s32_1 %s32_1
-)");
-}
-
-TEST_P(MarkvTest, F32Dot) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%dot2_1 = OpDot %f32 %f32vec2_01 %f32vec2_12
-%dot2_2 = OpDot %f32 %f32vec2_01 %f32vec2_01
-%dot2_3 = OpDot %f32 %f32vec2_12 %f32vec2_12
-%dot3_1 = OpDot %f32 %f32vec3_012 %f32vec3_123
-%dot3_2 = OpDot %f32 %f32vec3_012 %f32vec3_012
-%dot3_3 = OpDot %f32 %f32vec3_123 %f32vec3_123
-%dot4_1 = OpDot %f32 %f32vec4_0123 %f32vec4_1234
-%dot4_2 = OpDot %f32 %f32vec4_0123 %f32vec4_0123
-%dot4_3 = OpDot %f32 %f32vec4_1234 %f32vec4_1234
-)");
-}
-
-TEST_P(MarkvTest, F32VectorCompositeConstruct) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%cc1 = OpCompositeConstruct %f32vec4 %f32vec2_01 %f32vec2_12
-%cc2 = OpCompositeConstruct %f32vec3 %f32vec2_01 %f32_2
-%cc3 = OpCompositeConstruct %f32vec2 %f32_1 %f32_2
-%cc4 = OpCompositeConstruct %f32vec4 %f32_1 %f32_2 %cc3
-)");
-}
-
-TEST_P(MarkvTest, U32VectorCompositeConstruct) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12
-%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2
-%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2
-%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3
-)");
-}
-
-TEST_P(MarkvTest, S32VectorCompositeConstruct) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12
-%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2
-%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2
-%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3
-)");
-}
-
-TEST_P(MarkvTest, F32VectorCompositeExtract) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
-%f32vec3_013 = OpCompositeExtract %f32vec3 %f32vec4_0123 0 1 3
-)");
-}
-
-TEST_P(MarkvTest, F32VectorComparison) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
-%c1 = OpFOrdEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c2 = OpFUnordEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c3 = OpFOrdNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c4 = OpFUnordNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c5 = OpFOrdLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c6 = OpFUnordLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c7 = OpFOrdGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c8 = OpFUnordGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c9 = OpFOrdLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c10 = OpFUnordLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c11 = OpFOrdGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-%c12 = OpFUnordGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210
-)");
-}
-
-TEST_P(MarkvTest, VectorShuffle) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
-%sh1 = OpVectorShuffle %f32vec2 %f32vec4_0123 %f32vec4_3210 3 6
-%sh2 = OpVectorShuffle %f32vec3 %f32vec2_01 %f32vec4_3210 0 3 4
-)");
-}
-
-TEST_P(MarkvTest, VectorTimesScalar) {
-  TestEncodeDecodeShaderMainBody(GetParam(), R"(
-%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0
-%res1 = OpVectorTimesScalar %f32vec4 %f32vec4_0123 %f32_2
-%res2 = OpVectorTimesScalar %f32vec4 %f32vec4_3210 %f32_2
-)");
-}
-
-TEST_P(MarkvTest, SpirvSpecSample) {
-  TestEncodeDecode(GetParam(), R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %31 %33 %42 %57
-               OpExecutionMode %4 OriginLowerLeft
-
-; Debug information
-               OpSource GLSL 450
-               OpName %4 "main"
-               OpName %9 "scale"
-               OpName %17 "S"
-               OpMemberName %17 0 "b"
-               OpMemberName %17 1 "v"
-               OpMemberName %17 2 "i"
-               OpName %18 "blockName"
-               OpMemberName %18 0 "s"
-               OpMemberName %18 1 "cond"
-               OpName %20 ""
-               OpName %31 "color"
-               OpName %33 "color1"
-               OpName %42 "color2"
-               OpName %48 "i"
-               OpName %57 "multiplier"
-
-; Annotations (non-debug)
-               OpDecorate %15 ArrayStride 16
-               OpMemberDecorate %17 0 Offset 0
-               OpMemberDecorate %17 1 Offset 16
-               OpMemberDecorate %17 2 Offset 96
-               OpMemberDecorate %18 0 Offset 0
-               OpMemberDecorate %18 1 Offset 112
-               OpDecorate %18 Block
-               OpDecorate %20 DescriptorSet 0
-               OpDecorate %42 NoPerspective
-
-; All types, variables, and constants
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2                      ; void ()
-          %6 = OpTypeFloat 32                         ; 32-bit float
-          %7 = OpTypeVector %6 4                      ; vec4
-          %8 = OpTypePointer Function %7              ; function-local vec4*
-         %10 = OpConstant %6 1
-         %11 = OpConstant %6 2
-         %12 = OpConstantComposite %7 %10 %10 %11 %10 ; vec4(1.0, 1.0, 2.0, 1.0)
-         %13 = OpTypeInt 32 0                         ; 32-bit int, sign-less
-         %14 = OpConstant %13 5
-         %15 = OpTypeArray %7 %14
-         %16 = OpTypeInt 32 1
-         %17 = OpTypeStruct %13 %15 %16
-         %18 = OpTypeStruct %17 %13
-         %19 = OpTypePointer Uniform %18
-         %20 = OpVariable %19 Uniform
-         %21 = OpConstant %16 1
-         %22 = OpTypePointer Uniform %13
-         %25 = OpTypeBool
-         %26 = OpConstant %13 0
-         %30 = OpTypePointer Output %7
-         %31 = OpVariable %30 Output
-         %32 = OpTypePointer Input %7
-         %33 = OpVariable %32 Input
-         %35 = OpConstant %16 0
-         %36 = OpConstant %16 2
-         %37 = OpTypePointer Uniform %7
-         %42 = OpVariable %32 Input
-         %47 = OpTypePointer Function %16
-         %55 = OpConstant %16 4
-         %57 = OpVariable %32 Input
-
-; All functions
-          %4 = OpFunction %2 None %3                  ; main()
-          %5 = OpLabel
-          %9 = OpVariable %8 Function
-         %48 = OpVariable %47 Function
-               OpStore %9 %12
-         %23 = OpAccessChain %22 %20 %21              ; location of cond
-         %24 = OpLoad %13 %23                         ; load 32-bit int from cond
-         %27 = OpINotEqual %25 %24 %26                ; convert to bool
-               OpSelectionMerge %29 None              ; structured if
-               OpBranchConditional %27 %28 %41        ; if cond
-         %28 = OpLabel                                ; then
-         %34 = OpLoad %7 %33
-         %38 = OpAccessChain %37 %20 %35 %21 %36      ; s.v[2]
-         %39 = OpLoad %7 %38
-         %40 = OpFAdd %7 %34 %39
-               OpStore %31 %40
-               OpBranch %29
-         %41 = OpLabel                                ; else
-         %43 = OpLoad %7 %42
-         %44 = OpExtInst %7 %1 Sqrt %43               ; extended instruction sqrt
-         %45 = OpLoad %7 %9
-         %46 = OpFMul %7 %44 %45
-               OpStore %31 %46
-               OpBranch %29
-         %29 = OpLabel                                ; endif
-               OpStore %48 %35
-               OpBranch %49
-         %49 = OpLabel
-               OpLoopMerge %51 %52 None               ; structured loop
-               OpBranch %53
-         %53 = OpLabel
-         %54 = OpLoad %16 %48
-         %56 = OpSLessThan %25 %54 %55                ; i < 4 ?
-               OpBranchConditional %56 %50 %51        ; body or break
-         %50 = OpLabel                                ; body
-         %58 = OpLoad %7 %57
-         %59 = OpLoad %7 %31
-         %60 = OpFMul %7 %59 %58
-               OpStore %31 %60
-               OpBranch %52
-         %52 = OpLabel                                ; continue target
-         %61 = OpLoad %16 %48
-         %62 = OpIAdd %16 %61 %21                     ; ++i
-               OpStore %48 %62
-               OpBranch %49                           ; loop back
-         %51 = OpLabel                                ; loop merge point
-               OpReturn
-               OpFunctionEnd
-)");
-}
-
-TEST_P(MarkvTest, SampleFromDeadBranchEliminationTest) {
-  TestEncodeDecode(GetParam(), R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %gl_FragColor "gl_FragColor"
-%void = OpTypeVoid
-%5 = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%float_0 = OpConstant %float 0
-%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-%float_1 = OpConstant %float 1
-%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%main = OpFunction %void None %5
-%17 = OpLabel
-OpSelectionMerge %18 None
-OpBranchConditional %true %19 %20
-%19 = OpLabel
-OpBranch %18
-%20 = OpLabel
-OpBranch %18
-%18 = OpLabel
-%21 = OpPhi %v4float %12 %19 %14 %20
-OpStore %gl_FragColor %21
-OpReturn
-OpFunctionEnd
-)");
-}
-
-INSTANTIATE_TEST_CASE_P(AllMarkvModels, MarkvTest,
-                        ::testing::ValuesIn(std::vector<MarkvModelType>{
-                            kMarkvModelShaderLite,
-                            kMarkvModelShaderMid,
-                            kMarkvModelShaderMax,
-                        }), );
-
-}  // namespace
-}  // namespace comp
-}  // namespace spvtools
diff --git a/test/enum_set_test.cpp b/test/enum_set_test.cpp
index ddacd42..047d642 100644
--- a/test/enum_set_test.cpp
+++ b/test/enum_set_test.cpp
@@ -267,24 +267,24 @@
   EXPECT_THAT(ElementsIn(assigned), Eq(GetParam().expected));
 }
 
-INSTANTIATE_TEST_CASE_P(Samples, CapabilitySetForEachTest,
-                        ValuesIn(std::vector<ForEachCase>{
-                            {{}, {}},
-                            {{SpvCapabilityMatrix}, {SpvCapabilityMatrix}},
-                            {{SpvCapabilityKernel, SpvCapabilityShader},
-                             {SpvCapabilityShader, SpvCapabilityKernel}},
-                            {{static_cast<SpvCapability>(999)},
-                             {static_cast<SpvCapability>(999)}},
-                            {{static_cast<SpvCapability>(0x7fffffff)},
-                             {static_cast<SpvCapability>(0x7fffffff)}},
-                            // Mixture and out of order
-                            {{static_cast<SpvCapability>(0x7fffffff),
-                              static_cast<SpvCapability>(100),
-                              SpvCapabilityShader, SpvCapabilityMatrix},
-                             {SpvCapabilityMatrix, SpvCapabilityShader,
-                              static_cast<SpvCapability>(100),
-                              static_cast<SpvCapability>(0x7fffffff)}},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Samples, CapabilitySetForEachTest,
+                         ValuesIn(std::vector<ForEachCase>{
+                             {{}, {}},
+                             {{SpvCapabilityMatrix}, {SpvCapabilityMatrix}},
+                             {{SpvCapabilityKernel, SpvCapabilityShader},
+                              {SpvCapabilityShader, SpvCapabilityKernel}},
+                             {{static_cast<SpvCapability>(999)},
+                              {static_cast<SpvCapability>(999)}},
+                             {{static_cast<SpvCapability>(0x7fffffff)},
+                              {static_cast<SpvCapability>(0x7fffffff)}},
+                             // Mixture and out of order
+                             {{static_cast<SpvCapability>(0x7fffffff),
+                               static_cast<SpvCapability>(100),
+                               SpvCapabilityShader, SpvCapabilityMatrix},
+                              {SpvCapabilityMatrix, SpvCapabilityShader,
+                               static_cast<SpvCapability>(100),
+                               static_cast<SpvCapability>(0x7fffffff)}},
+                         }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/enum_string_mapping_test.cpp b/test/enum_string_mapping_test.cpp
index b525d60..a0379c1 100644
--- a/test/enum_string_mapping_test.cpp
+++ b/test/enum_string_mapping_test.cpp
@@ -64,7 +64,7 @@
   EXPECT_EQ(capability_str, result_str);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllExtensions, ExtensionTest,
     ValuesIn(std::vector<std::pair<Extension, std::string>>({
         {Extension::kSPV_KHR_16bit_storage, "SPV_KHR_16bit_storage"},
@@ -89,13 +89,13 @@
         {Extension::kSPV_KHR_8bit_storage, "SPV_KHR_8bit_storage"},
     })));
 
-INSTANTIATE_TEST_CASE_P(UnknownExtensions, UnknownExtensionTest,
-                        Values("", "SPV_KHR_", "SPV_KHR_device_group_ERROR",
-                               /*alphabetically before all extensions*/ "A",
-                               /*alphabetically after all extensions*/ "Z",
-                               "SPV_ERROR_random_string_hfsdklhlktherh"));
+INSTANTIATE_TEST_SUITE_P(UnknownExtensions, UnknownExtensionTest,
+                         Values("", "SPV_KHR_", "SPV_KHR_device_group_ERROR",
+                                /*alphabetically before all extensions*/ "A",
+                                /*alphabetically after all extensions*/ "Z",
+                                "SPV_ERROR_random_string_hfsdklhlktherh"));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllCapabilities, CapabilityTest,
     ValuesIn(std::vector<std::pair<SpvCapability, std::string>>(
         {{SpvCapabilityMatrix, "Matrix"},
@@ -189,7 +189,7 @@
           "ShaderViewportIndexLayerEXT"},
          {SpvCapabilityShaderViewportMaskNV, "ShaderViewportMaskNV"},
          {SpvCapabilityShaderStereoViewNV, "ShaderStereoViewNV"},
-         {SpvCapabilityPerViewAttributesNV, "PerViewAttributesNV"}})), );
+         {SpvCapabilityPerViewAttributesNV, "PerViewAttributesNV"}})));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/ext_inst.debuginfo_test.cpp b/test/ext_inst.debuginfo_test.cpp
index 15fa8f7..ec012e0 100644
--- a/test/ext_inst.debuginfo_test.cpp
+++ b/test/ext_inst.debuginfo_test.cpp
@@ -368,30 +368,30 @@
   }
 
 // DebugInfo 4.1 Absent Debugging Information
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInfoNone, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(InfoNone),  // enum value 0
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInfoNone, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(InfoNone),  // enum value 0
+                         })));
 
 // DebugInfo 4.2 Compilation Unit
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugCompilationUnit,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILL(CompilationUnit, 100, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugCompilationUnit,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILL(CompilationUnit, 100, 42),
+                         })));
 
 // DebugInfo 4.3 Type instructions
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeBasic, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIE(TypeBasic, Unspecified),
-                            CASE_IIE(TypeBasic, Address),
-                            CASE_IIE(TypeBasic, Boolean),
-                            CASE_IIE(TypeBasic, Float),
-                            CASE_IIE(TypeBasic, Signed),
-                            CASE_IIE(TypeBasic, SignedChar),
-                            CASE_IIE(TypeBasic, Unsigned),
-                            CASE_IIE(TypeBasic, UnsignedChar),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeBasic, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIE(TypeBasic, Unspecified),
+                             CASE_IIE(TypeBasic, Address),
+                             CASE_IIE(TypeBasic, Boolean),
+                             CASE_IIE(TypeBasic, Float),
+                             CASE_IIE(TypeBasic, Signed),
+                             CASE_IIE(TypeBasic, SignedChar),
+                             CASE_IIE(TypeBasic, Unsigned),
+                             CASE_IIE(TypeBasic, UnsignedChar),
+                         })));
 
 // The FlagIsPublic is value is (1 << 0) | (1 << 2) which is the same
 // as the bitwise-OR of FlagIsProtected and FlagIsPrivate.
@@ -417,7 +417,7 @@
   EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(expected)) << input;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypePointer, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
 
@@ -463,49 +463,50 @@
                 uint32_t(DebugInfoFlagIndirectVariable) |
                 uint32_t(DebugInfoFlagIsOptimized)),
 
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeQualifier,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IE(TypeQualifier, ConstType),
-                            CASE_IE(TypeQualifier, VolatileType),
-                            CASE_IE(TypeQualifier, RestrictType),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeQualifier,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IE(TypeQualifier, ConstType),
+                             CASE_IE(TypeQualifier, VolatileType),
+                             CASE_IE(TypeQualifier, RestrictType),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeArray, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypeArray),
-                            CASE_III(TypeArray),
-                            CASE_IIII(TypeArray),
-                            CASE_IIIII(TypeArray),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeArray, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypeArray),
+                             CASE_III(TypeArray),
+                             CASE_IIII(TypeArray),
+                             CASE_IIIII(TypeArray),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeVector, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IL(TypeVector, 2),
-                            CASE_IL(TypeVector, 3),
-                            CASE_IL(TypeVector, 4),
-                            CASE_IL(TypeVector, 16),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeVector,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IL(TypeVector, 2),
+                             CASE_IL(TypeVector, 3),
+                             CASE_IL(TypeVector, 4),
+                             CASE_IL(TypeVector, 16),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypedef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLI(Typedef, 12, 13),
-                            CASE_IIILLI(Typedef, 14, 99),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypedef, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILLI(Typedef, 12, 13),
+                             CASE_IIILLI(Typedef, 14, 99),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeFunction,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_I(TypeFunction),
-                            CASE_II(TypeFunction),
-                            CASE_III(TypeFunction),
-                            CASE_IIII(TypeFunction),
-                            CASE_IIIII(TypeFunction),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeFunction,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_I(TypeFunction),
+                             CASE_II(TypeFunction),
+                             CASE_III(TypeFunction),
+                             CASE_IIII(TypeFunction),
+                             CASE_IIIII(TypeFunction),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeEnum, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIFII(
@@ -518,9 +519,9 @@
                           uint32_t(DebugInfoFlagStaticMember)),
         CASE_IIILLIIFIIIIII(TypeEnum, 99, 1, "FlagStaticMember",
                             uint32_t(DebugInfoFlagStaticMember)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeComposite, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IEILLIIF(
@@ -545,22 +546,22 @@
                          uint32_t(DebugInfoFlagIsPrivate)),
         CASE_IEILLIIFIIII(TypeComposite, Class, 9, 10, "FlagIsPrivate",
                           uint32_t(DebugInfoFlagIsPrivate)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeMember, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLIIIF(TypeMember, 12, 13, "FlagIsPrivate",
-                                           uint32_t(DebugInfoFlagIsPrivate)),
-                            CASE_IIILLIIIF(TypeMember, 99, 100,
-                                           "FlagIsPrivate|FlagFwdDecl",
-                                           uint32_t(DebugInfoFlagIsPrivate) |
-                                               uint32_t(DebugInfoFlagFwdDecl)),
-                            // Add the optional Id argument.
-                            CASE_IIILLIIIFI(TypeMember, 12, 13, "FlagIsPrivate",
-                                            uint32_t(DebugInfoFlagIsPrivate)),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(
+    DebugInfoDebugTypeMember, ExtInstDebugInfoRoundTripTest,
+    ::testing::ValuesIn(std::vector<InstructionCase>({
+        CASE_IIILLIIIF(TypeMember, 12, 13, "FlagIsPrivate",
+                       uint32_t(DebugInfoFlagIsPrivate)),
+        CASE_IIILLIIIF(TypeMember, 99, 100, "FlagIsPrivate|FlagFwdDecl",
+                       uint32_t(DebugInfoFlagIsPrivate) |
+                           uint32_t(DebugInfoFlagFwdDecl)),
+        // Add the optional Id argument.
+        CASE_IIILLIIIFI(TypeMember, 12, 13, "FlagIsPrivate",
+                        uint32_t(DebugInfoFlagIsPrivate)),
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeInheritance, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIIIF(TypeInheritance, "FlagIsPrivate",
@@ -568,53 +569,53 @@
         CASE_IIIIF(TypeInheritance, "FlagIsPrivate|FlagFwdDecl",
                    uint32_t(DebugInfoFlagIsPrivate) |
                        uint32_t(DebugInfoFlagFwdDecl)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypePtrToMember,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypePtrToMember),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypePtrToMember,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypePtrToMember),
+                         })));
 
 // DebugInfo 4.4 Templates
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplate,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypeTemplate),
-                            CASE_III(TypeTemplate),
-                            CASE_IIII(TypeTemplate),
-                            CASE_IIIII(TypeTemplate),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplate,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypeTemplate),
+                             CASE_III(TypeTemplate),
+                             CASE_IIII(TypeTemplate),
+                             CASE_IIIII(TypeTemplate),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameter,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIIILL(TypeTemplateParameter, 1, 2),
-                            CASE_IIIILL(TypeTemplateParameter, 99, 102),
-                            CASE_IIIILL(TypeTemplateParameter, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateParameter,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIIILL(TypeTemplateParameter, 1, 2),
+                             CASE_IIIILL(TypeTemplateParameter, 99, 102),
+                             CASE_IIIILL(TypeTemplateParameter, 10, 7),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateTemplateParameter,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILL(TypeTemplateTemplateParameter, 1, 2),
-                            CASE_IIILL(TypeTemplateTemplateParameter, 99, 102),
-                            CASE_IIILL(TypeTemplateTemplateParameter, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateTemplateParameter,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILL(TypeTemplateTemplateParameter, 1, 2),
+                             CASE_IIILL(TypeTemplateTemplateParameter, 99, 102),
+                             CASE_IIILL(TypeTemplateTemplateParameter, 10, 7),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameterPack,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IILLI(TypeTemplateParameterPack, 1, 2),
-                            CASE_IILLII(TypeTemplateParameterPack, 99, 102),
-                            CASE_IILLIII(TypeTemplateParameterPack, 10, 7),
-                            CASE_IILLIIII(TypeTemplateParameterPack, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateParameterPack,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IILLI(TypeTemplateParameterPack, 1, 2),
+                             CASE_IILLII(TypeTemplateParameterPack, 99, 102),
+                             CASE_IILLIII(TypeTemplateParameterPack, 10, 7),
+                             CASE_IILLIIII(TypeTemplateParameterPack, 10, 7),
+                         })));
 
 // DebugInfo 4.5 Global Variables
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugGlobalVariable, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIIF(GlobalVariable, 1, 2, "FlagIsOptimized",
@@ -625,20 +626,20 @@
                         uint32_t(DebugInfoFlagIsOptimized)),
         CASE_IIILLIIIFI(GlobalVariable, 42, 43, "FlagIsOptimized",
                         uint32_t(DebugInfoFlagIsOptimized)),
-    })), );
+    })));
 
 // DebugInfo 4.6 Functions
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugFunctionDeclaration, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIF(FunctionDeclaration, 1, 2, "FlagIsOptimized",
                       uint32_t(DebugInfoFlagIsOptimized)),
         CASE_IIILLIIF(FunctionDeclaration, 42, 43, "FlagFwdDecl",
                       uint32_t(DebugInfoFlagFwdDecl)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugFunction, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIFLI(Function, 1, 2, "FlagIsOptimized",
@@ -650,65 +651,65 @@
                          uint32_t(DebugInfoFlagIsOptimized), 3),
         CASE_IIILLIIFLII(Function, 42, 43, "FlagFwdDecl",
                          uint32_t(DebugInfoFlagFwdDecl), 44),
-    })), );
+    })));
 
 // DebugInfo 4.7 Local Information
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlock,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILLII(LexicalBlock, 1, 2),
-                            CASE_ILLII(LexicalBlock, 42, 43),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLexicalBlock,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILLII(LexicalBlock, 1, 2),
+                             CASE_ILLII(LexicalBlock, 42, 43),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlockDiscriminator,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(LexicalBlockDiscriminator, 1),
-                            CASE_ILI(LexicalBlockDiscriminator, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLexicalBlockDiscriminator,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(LexicalBlockDiscriminator, 1),
+                             CASE_ILI(LexicalBlockDiscriminator, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugScope, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_I(Scope),
-                            CASE_II(Scope),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugScope, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_I(Scope),
+                             CASE_II(Scope),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugNoScope, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(NoScope),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugNoScope, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(NoScope),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedAt, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_LII(InlinedAt, 1),
-                            CASE_LII(InlinedAt, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInlinedAt, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_LII(InlinedAt, 1),
+                             CASE_LII(InlinedAt, 42),
+                         })));
 
 // DebugInfo 4.8 Local Variables
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLocalVariable,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLI(LocalVariable, 1, 2),
-                            CASE_IIILLI(LocalVariable, 42, 43),
-                            CASE_IIILLIL(LocalVariable, 1, 2, 3),
-                            CASE_IIILLIL(LocalVariable, 42, 43, 44),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLocalVariable,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILLI(LocalVariable, 1, 2),
+                             CASE_IIILLI(LocalVariable, 42, 43),
+                             CASE_IIILLIL(LocalVariable, 1, 2, 3),
+                             CASE_IIILLIL(LocalVariable, 42, 43, 44),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedVariable,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(InlinedVariable),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInlinedVariable,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(InlinedVariable),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugDeclare,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_III(Declare),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugDeclare,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_III(Declare),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugDebugValue, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_III(Value),
@@ -717,53 +718,54 @@
         CASE_IIIIII(Value),
         // Test up to 4 id parameters. We can always try more.
         CASE_IIIIIII(Value),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugOperation,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_E(Operation, Deref),
-                            CASE_E(Operation, Plus),
-                            CASE_E(Operation, Minus),
-                            CASE_EL(Operation, PlusUconst, 1),
-                            CASE_EL(Operation, PlusUconst, 42),
-                            CASE_ELL(Operation, BitPiece, 1, 2),
-                            CASE_ELL(Operation, BitPiece, 4, 5),
-                            CASE_E(Operation, Swap),
-                            CASE_E(Operation, Xderef),
-                            CASE_E(Operation, StackValue),
-                            CASE_EL(Operation, Constu, 1),
-                            CASE_EL(Operation, Constu, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugOperation,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_E(Operation, Deref),
+                             CASE_E(Operation, Plus),
+                             CASE_E(Operation, Minus),
+                             CASE_EL(Operation, PlusUconst, 1),
+                             CASE_EL(Operation, PlusUconst, 42),
+                             CASE_ELL(Operation, BitPiece, 1, 2),
+                             CASE_ELL(Operation, BitPiece, 4, 5),
+                             CASE_E(Operation, Swap),
+                             CASE_E(Operation, Xderef),
+                             CASE_E(Operation, StackValue),
+                             CASE_EL(Operation, Constu, 1),
+                             CASE_EL(Operation, Constu, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugExpression,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(Expression),
-                            CASE_I(Expression),
-                            CASE_II(Expression),
-                            CASE_III(Expression),
-                            CASE_IIII(Expression),
-                            CASE_IIIII(Expression),
-                            CASE_IIIIII(Expression),
-                            CASE_IIIIIII(Expression),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugExpression,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(Expression),
+                             CASE_I(Expression),
+                             CASE_II(Expression),
+                             CASE_III(Expression),
+                             CASE_IIII(Expression),
+                             CASE_IIIII(Expression),
+                             CASE_IIIIII(Expression),
+                             CASE_IIIIIII(Expression),
+                         })));
 
 // DebugInfo 4.9 Macros
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroDef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(MacroDef, 1),
-                            CASE_ILI(MacroDef, 42),
-                            CASE_ILII(MacroDef, 1),
-                            CASE_ILII(MacroDef, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugMacroDef, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(MacroDef, 1),
+                             CASE_ILI(MacroDef, 42),
+                             CASE_ILII(MacroDef, 1),
+                             CASE_ILII(MacroDef, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroUndef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(MacroUndef, 1),
-                            CASE_ILI(MacroUndef, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugMacroUndef,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(MacroUndef, 1),
+                             CASE_ILI(MacroUndef, 42),
+                         })));
 
 #undef CASE_0
 #undef CASE_ILL
diff --git a/test/ext_inst.glsl_test.cpp b/test/ext_inst.glsl_test.cpp
index f52337c..41d222f 100644
--- a/test/ext_inst.glsl_test.cpp
+++ b/test/ext_inst.glsl_test.cpp
@@ -106,7 +106,7 @@
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExtInstParameters, ExtInstGLSLstd450RoundTripTest,
     ::testing::ValuesIn(std::vector<ExtInstContext>({
         // We are only testing the correctness of encoding and decoding here.
@@ -198,7 +198,7 @@
         {"NMin", "%5 %5", 79, 7, {5, 5}},
         {"NMax", "%5 %5", 80, 7, {5, 5}},
         {"NClamp", "%5 %5 %5", 81, 8, {5, 5, 5}},
-    })), );
+    })));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/ext_inst.opencl_test.cpp b/test/ext_inst.opencl_test.cpp
index 06bc5e8..7dd903e 100644
--- a/test/ext_inst.opencl_test.cpp
+++ b/test/ext_inst.opencl_test.cpp
@@ -90,7 +90,7 @@
 
 // clang-format off
 // OpenCL.std: 2.1 Math extended instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMath, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         // We are only testing the correctness of encoding and decoding here.
@@ -190,10 +190,10 @@
         CASE1(Native_sin, native_sin),
         CASE1(Native_sqrt, native_sqrt),
         CASE1(Native_tan, native_tan), // enum value 94
-    })),);
+    })));
 
 // OpenCL.std: 2.1 Integer instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLInteger, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE1(SAbs, s_abs), // enum value 141
@@ -230,10 +230,10 @@
         CASE2(UAbs_diff, u_abs_diff),
         CASE2(UMul_hi, u_mul_hi),
         CASE3(UMad_hi, u_mad_hi), // enum value 204
-    })),);
+    })));
 
 // OpenCL.std: 2.3 Common instrucitons
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLCommon, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE3(FClamp, fclamp), // enum value 95
@@ -245,10 +245,10 @@
         CASE2(Step, step),
         CASE3(Smoothstep, smoothstep),
         CASE1(Sign, sign), // enum value 103
-    })),);
+    })));
 
 // OpenCL.std: 2.4 Geometric instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLGeometric, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Cross, cross), // enum value 104
@@ -258,18 +258,18 @@
         CASE2(Fast_distance, fast_distance),
         CASE1(Fast_length, fast_length),
         CASE1(Fast_normalize, fast_normalize), // enum value 110
-    })),);
+    })));
 
 // OpenCL.std: 2.5 Relational instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLRelational, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE3(Bitselect, bitselect), // enum value 186
         CASE3(Select, select), // enum value 187
-    })),);
+    })));
 
 // OpenCL.std: 2.6 Vector data load and store instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLVectorLoadStore, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         // The last argument to Vloadn must be one of 2, 3, 4, 8, 16.
@@ -306,20 +306,20 @@
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTZ),
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTP),
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTN),
-    })),);
+    })));
 
 // OpenCL.std: 2.7 Miscellaneous vector instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscellaneousVector, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Shuffle, shuffle),
         CASE3(Shuffle2, shuffle2),
-    })),);
+    })));
 
 // OpenCL.std: 2.8 Miscellaneous instructions
 
 #define PREFIX uint32_t(OpenCLLIB::Entrypoints::Printf), "printf"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscPrintf, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
       // Printf is interesting because it takes a variable number of arguments.
@@ -338,14 +338,14 @@
         {4, 5, 6, 7, 8, 9, 10, 11, 12, 13}},
       {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14",
         {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}},
-    })),);
+    })));
 #undef PREFIX
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscPrefetch, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Prefetch, prefetch),
-    })),);
+    })));
 
 // OpenCL.std: 2.9.1 Image encoding
 // No new instructions defined in this section.
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..c0e2925
--- /dev/null
+++ b/test/fuzz/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright (c) 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+if (${SPIRV_BUILD_FUZZER})
+
+  set(SOURCES
+          fuzz_test_util.h
+
+          fuzzer_replayer_test.cpp
+          fuzzer_shrinker_test.cpp
+          fact_manager_test.cpp
+          fuzz_test_util.cpp
+          fuzzer_pass_add_useful_constructs_test.cpp
+          transformation_add_constant_boolean_test.cpp
+          transformation_add_constant_scalar_test.cpp
+          transformation_add_dead_break_test.cpp
+          transformation_add_dead_continue_test.cpp
+          transformation_add_type_boolean_test.cpp
+          transformation_add_type_float_test.cpp
+          transformation_add_type_int_test.cpp
+          transformation_add_type_pointer_test.cpp
+          transformation_move_block_down_test.cpp
+          transformation_replace_boolean_constant_with_constant_binary_test.cpp
+          transformation_replace_constant_with_uniform_test.cpp
+          transformation_split_block_test.cpp
+          uniform_buffer_element_descriptor_test.cpp)
+
+  add_spvtools_unittest(TARGET fuzz
+        SRCS ${SOURCES}
+        LIBS SPIRV-Tools-fuzz
+        )
+endif()
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
new file mode 100644
index 0000000..738f8c9
--- /dev/null
+++ b/test/fuzz/fact_manager_test.cpp
@@ -0,0 +1,743 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <limits>
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+using opt::analysis::BoolConstant;
+using opt::analysis::FloatConstant;
+using opt::analysis::IntConstant;
+using opt::analysis::ScalarConstant;
+
+using opt::analysis::Bool;
+using opt::analysis::Float;
+using opt::analysis::Integer;
+using opt::analysis::Type;
+
+bool AddFactHelper(
+    FactManager* fact_manager, opt::IRContext* context,
+    std::vector<uint32_t>&& words,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  for (auto word : words) {
+    constant_uniform_fact.add_constant_word(word);
+  }
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->AddFact(fact, context);
+}
+
+TEST(FactManagerTest, ConstantsAvailableViaUniforms) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpDecorate %100 DescriptorSet 0
+               OpDecorate %100 Binding 0
+               OpDecorate %200 DescriptorSet 0
+               OpDecorate %200 Binding 1
+               OpDecorate %300 DescriptorSet 0
+               OpDecorate %300 Binding 2
+               OpDecorate %400 DescriptorSet 0
+               OpDecorate %400 Binding 3
+               OpDecorate %500 DescriptorSet 0
+               OpDecorate %500 Binding 4
+               OpDecorate %600 DescriptorSet 0
+               OpDecorate %600 Binding 5
+               OpDecorate %700 DescriptorSet 0
+               OpDecorate %700 Binding 6
+               OpDecorate %800 DescriptorSet 1
+               OpDecorate %800 Binding 0
+               OpDecorate %900 DescriptorSet 1
+               OpDecorate %900 Binding 1
+               OpDecorate %1000 DescriptorSet 1
+               OpDecorate %1000 Binding 2
+               OpDecorate %1100 DescriptorSet 1
+               OpDecorate %1100 Binding 3
+               OpDecorate %1200 DescriptorSet 1
+               OpDecorate %1200 Binding 4
+               OpDecorate %1300 DescriptorSet 1
+               OpDecorate %1300 Binding 5
+               OpDecorate %1400 DescriptorSet 1
+               OpDecorate %1400 Binding 6
+               OpDecorate %1500 DescriptorSet 2
+               OpDecorate %1500 Binding 0
+               OpDecorate %1600 DescriptorSet 2
+               OpDecorate %1600 Binding 1
+               OpDecorate %1700 DescriptorSet 2
+               OpDecorate %1700 Binding 2
+               OpDecorate %1800 DescriptorSet 2
+               OpDecorate %1800 Binding 3
+               OpDecorate %1900 DescriptorSet 2
+               OpDecorate %1900 Binding 4
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeInt 64 0
+         %13 = OpTypeInt 64 1
+         %15 = OpTypeFloat 32
+         %16 = OpTypeFloat 64
+         %17 = OpConstant %11 5
+         %18 = OpConstant %11 20
+         %19 = OpTypeVector %10 4
+         %20 = OpConstant %11 6
+         %21 = OpTypeVector %12 4
+         %22 = OpConstant %11 10
+         %23 = OpTypeVector %11 4
+
+        %102 = OpTypeStruct %10 %10 %23
+        %101 = OpTypePointer Uniform %102
+        %100 = OpVariable %101 Uniform
+
+        %203 = OpTypeArray %23 %17
+        %202 = OpTypeArray %203 %18
+        %201 = OpTypePointer Uniform %202
+        %200 = OpVariable %201 Uniform
+
+        %305 = OpTypeStruct %16 %16 %16 %11 %16
+        %304 = OpTypeStruct %16 %16 %305
+        %303 = OpTypeStruct %304
+        %302 = OpTypeStruct %10 %303
+        %301 = OpTypePointer Uniform %302
+        %300 = OpVariable %301 Uniform
+
+        %400 = OpVariable %101 Uniform
+
+        %500 = OpVariable %201 Uniform
+
+        %604 = OpTypeArray %13 %20
+        %603 = OpTypeArray %604 %20
+        %602 = OpTypeArray %603 %20
+        %601 = OpTypePointer Uniform %602
+        %600 = OpVariable %601 Uniform
+
+        %703 = OpTypeArray %13 %20
+        %702 = OpTypeArray %703 %20
+        %701 = OpTypePointer Uniform %702
+        %700 = OpVariable %701 Uniform
+
+        %802 = OpTypeStruct %702 %602 %19 %202 %302
+        %801 = OpTypePointer Uniform %802
+        %800 = OpVariable %801 Uniform
+
+        %902 = OpTypeStruct %702 %802 %19 %202 %302
+        %901 = OpTypePointer Uniform %902
+        %900 = OpVariable %901 Uniform
+
+       %1003 = OpTypeStruct %802
+       %1002 = OpTypeArray %1003 %20
+       %1001 = OpTypePointer Uniform %1002
+       %1000 = OpVariable %1001 Uniform
+
+       %1101 = OpTypePointer Uniform %21
+       %1100 = OpVariable %1101 Uniform
+
+       %1202 = OpTypeArray %21 %20
+       %1201 = OpTypePointer Uniform %1202
+       %1200 = OpVariable %1201 Uniform
+
+       %1302 = OpTypeArray %21 %20
+       %1301 = OpTypePointer Uniform %1302
+       %1300 = OpVariable %1301 Uniform
+
+       %1402 = OpTypeArray %15 %22
+       %1401 = OpTypePointer Uniform %1402
+       %1400 = OpVariable %1401 Uniform
+
+       %1501 = OpTypePointer Uniform %1402
+       %1500 = OpVariable %1501 Uniform
+
+       %1602 = OpTypeArray %1402 %22
+       %1601 = OpTypePointer Uniform %1602
+       %1600 = OpVariable %1601 Uniform
+
+       %1704 = OpTypeStruct %16 %16 %16
+       %1703 = OpTypeArray %1704 %22
+       %1702 = OpTypeArray %1703 %22
+       %1701 = OpTypePointer Uniform %1702
+       %1700 = OpVariable %1701 Uniform
+
+       %1800 = OpVariable %1701 Uniform
+
+       %1906 = OpTypeStruct %16
+       %1905 = OpTypeStruct %1906
+       %1904 = OpTypeStruct %1905
+       %1903 = OpTypeStruct %1904
+       %1902 = OpTypeStruct %1903
+       %1901 = OpTypePointer Uniform %1902
+       %1900 = OpVariable %1901 Uniform
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  uint32_t buffer_int32_min[1];
+  uint32_t buffer_int64_1[2];
+  uint32_t buffer_int64_max[2];
+  uint32_t buffer_uint64_1[2];
+  uint32_t buffer_uint64_max[2];
+  uint32_t buffer_float_10[1];
+  uint32_t buffer_double_10[2];
+  uint32_t buffer_double_20[2];
+
+  {
+    int32_t temp = std::numeric_limits<int32_t>::min();
+    std::memcpy(&buffer_int32_min, &temp, sizeof(temp));
+  }
+
+  {
+    int64_t temp = 1;
+    std::memcpy(&buffer_int64_1, &temp, sizeof(temp));
+  }
+
+  {
+    int64_t temp = std::numeric_limits<int64_t>::max();
+    std::memcpy(&buffer_int64_max, &temp, sizeof(temp));
+  }
+
+  {
+    uint64_t temp = 1;
+    std::memcpy(&buffer_uint64_1, &temp, sizeof(temp));
+  }
+
+  {
+    uint64_t temp = std::numeric_limits<uint64_t>::max();
+    std::memcpy(&buffer_uint64_max, &temp, sizeof(temp));
+  }
+
+  {
+    float temp = 10.0f;
+    std::memcpy(&buffer_float_10, &temp, sizeof(float));
+  }
+
+  {
+    double temp = 10.0;
+    std::memcpy(&buffer_double_10, &temp, sizeof(temp));
+  }
+
+  {
+    double temp = 20.0;
+    std::memcpy(&buffer_double_20, &temp, sizeof(temp));
+  }
+
+  FactManager fact_manager;
+
+  uint32_t type_int32_id = 11;
+  uint32_t type_int64_id = 13;
+  uint32_t type_uint32_id = 10;
+  uint32_t type_uint64_id = 12;
+  uint32_t type_float_id = 15;
+  uint32_t type_double_id = 16;
+
+  // Initially there should be no facts about uniforms.
+  ASSERT_TRUE(fact_manager
+                  .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                            type_uint32_id)
+                  .empty());
+
+  // In the comments that follow we write v[...][...] to refer to uniform
+  // variable v indexed with some given indices, when in practice v is
+  // identified via a (descriptor set, binding) pair.
+
+  // 100[2][3] == int(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+                            MakeUniformBufferElementDescriptor(0, 0, {2, 3})));
+
+  // 200[1][2][3] == int(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), {1},
+                    MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3})));
+
+  // 300[1][0][2][3] == int(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), {1},
+                    MakeUniformBufferElementDescriptor(0, 2, {1, 0, 2, 3})));
+
+  // 400[2][3] = int32_min
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
+                            MakeUniformBufferElementDescriptor(0, 3, {2, 3})));
+
+  // 500[1][2][3] = int32_min
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
+                    MakeUniformBufferElementDescriptor(0, 4, {1, 2, 3})));
+
+  // 600[1][2][3] = int64_max
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, context.get(), {buffer_int64_max[0], buffer_int64_max[1]},
+      MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3})));
+
+  // 700[1][1] = int64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+                            {buffer_int64_max[0], buffer_int64_max[1]},
+                            MakeUniformBufferElementDescriptor(0, 6, {1, 1})));
+
+  // 800[2][3] = uint(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+                            MakeUniformBufferElementDescriptor(1, 0, {2, 3})));
+
+  // 900[1][2][3] = uint(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), {1},
+                    MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3})));
+
+  // 1000[1][0][2][3] = uint(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), {1},
+                    MakeUniformBufferElementDescriptor(1, 2, {1, 0, 2, 3})));
+
+  // 1100[0] = uint64(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+                            {buffer_uint64_1[0], buffer_uint64_1[1]},
+                            MakeUniformBufferElementDescriptor(1, 3, {0})));
+
+  // 1200[0][0] = uint64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+                            {buffer_uint64_max[0], buffer_uint64_max[1]},
+                            MakeUniformBufferElementDescriptor(1, 4, {0, 0})));
+
+  // 1300[1][0] = uint64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
+                            {buffer_uint64_max[0], buffer_uint64_max[1]},
+                            MakeUniformBufferElementDescriptor(1, 5, {1, 0})));
+
+  // 1400[6] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(1, 6, {6})));
+
+  // 1500[7] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(2, 0, {7})));
+
+  // 1600[9][9] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(2, 1, {9, 9})));
+
+  // 1700[9][9][1] = double(10.0)
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
+      MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1})));
+
+  // 1800[9][9][2] = double(10.0)
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
+      MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})));
+
+  // 1900[0][0][0][0][0] = double(20.0)
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, context.get(), {buffer_double_20[0], buffer_double_20[1]},
+      MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})));
+
+  opt::Instruction::OperandList operands = {
+      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 50, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int32_min[0]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 51, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int64_id, 52, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint32_id, 53, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint64_id, 54, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint64_id, 55, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_float_10[0]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_float_id, 56, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_double_id, 57, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_double_id, 58, operands));
+
+  // A duplicate of the constant with id 59.
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 59, operands));
+
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
+  // Constants 1 and int32_min are available.
+  ASSERT_EQ(2, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_int32_id)
+                   .size());
+  // Constant int64_max is available.
+  ASSERT_EQ(1, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_int64_id)
+                   .size());
+  // Constant 1u is available.
+  ASSERT_EQ(1, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_uint32_id)
+                   .size());
+  // Constants 1u and uint64_max are available.
+  ASSERT_EQ(2, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_uint64_id)
+                   .size());
+  // Constant 10.0 is available.
+  ASSERT_EQ(1, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_float_id)
+                   .size());
+  // Constants 10.0 and 20.0 are available.
+  ASSERT_EQ(2, fact_manager
+                   .GetConstantsAvailableFromUniformsForType(context.get(),
+                                                             type_double_id)
+                   .size());
+
+  ASSERT_EQ(std::numeric_limits<int64_t>::max(),
+            context->get_constant_mgr()
+                ->FindDeclaredConstant(
+                    fact_manager.GetConstantsAvailableFromUniformsForType(
+                        context.get(), type_int64_id)[0])
+                ->AsIntConstant()
+                ->GetS64());
+  ASSERT_EQ(1, context->get_constant_mgr()
+                   ->FindDeclaredConstant(
+                       fact_manager.GetConstantsAvailableFromUniformsForType(
+                           context.get(), type_uint32_id)[0])
+                   ->AsIntConstant()
+                   ->GetU32());
+  ASSERT_EQ(10.0f,
+            context->get_constant_mgr()
+                ->FindDeclaredConstant(
+                    fact_manager.GetConstantsAvailableFromUniformsForType(
+                        context.get(), type_float_id)[0])
+                ->AsFloatConstant()
+                ->GetFloat());
+  const std::vector<uint32_t>& double_constant_ids =
+      fact_manager.GetConstantsAvailableFromUniformsForType(context.get(),
+                                                            type_double_id);
+  ASSERT_EQ(10.0, context->get_constant_mgr()
+                      ->FindDeclaredConstant(double_constant_ids[0])
+                      ->AsFloatConstant()
+                      ->GetDouble());
+  ASSERT_EQ(20.0, context->get_constant_mgr()
+                      ->FindDeclaredConstant(double_constant_ids[1])
+                      ->AsFloatConstant()
+                      ->GetDouble());
+
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+      descriptors_for_double_10 = fact_manager.GetUniformDescriptorsForConstant(
+          context.get(), double_constant_ids[0]);
+  ASSERT_EQ(2, descriptors_for_double_10.size());
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_10[0]));
+  }
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_10[1]));
+  }
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+      descriptors_for_double_20 = fact_manager.GetUniformDescriptorsForConstant(
+          context.get(), double_constant_ids[1]);
+  ASSERT_EQ(1, descriptors_for_double_20.size());
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_20[0]));
+  }
+
+  auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor(
+      context.get(), MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}));
+  ASSERT_TRUE(constant_1_id);
+
+  auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor(
+      context.get(), MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}));
+  ASSERT_TRUE(constant_2_id);
+
+  ASSERT_EQ(double_constant_ids[0], constant_1_id);
+
+  ASSERT_EQ(double_constant_ids[1], constant_2_id);
+}
+
+TEST(FactManagerTest, TwoConstantsWithSameValue) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "buf"
+               OpMemberName %10 0 "a"
+               OpName %12 ""
+               OpDecorate %8 RelaxedPrecision
+               OpMemberDecorate %10 0 RelaxedPrecision
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %20 = OpConstant %6 1
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto uniform_buffer_element_descriptor =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  // (0, 0, [0]) = int(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
+                            uniform_buffer_element_descriptor));
+  auto constants =
+      fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6);
+  ASSERT_EQ(1, constants.size());
+  ASSERT_TRUE(constants[0] == 9 || constants[0] == 20);
+
+  auto constant = fact_manager.GetConstantFromUniformDescriptor(
+      context.get(), uniform_buffer_element_descriptor);
+  ASSERT_TRUE(constant == 9 || constant == 20);
+
+  // Because the constants with ids 9 and 20 are equal, we should get the same
+  // single uniform buffer element descriptor when we look up the descriptors
+  // for either one of them.
+  for (auto constant_id : {9u, 20u}) {
+    auto descriptors = fact_manager.GetUniformDescriptorsForConstant(
+        context.get(), constant_id);
+    ASSERT_EQ(1, descriptors.size());
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &uniform_buffer_element_descriptor, &descriptors[0]));
+  }
+}
+
+TEST(FactManagerTest, NonFiniteFactsAreNotValid) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpMemberName %7 1 "d"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 8
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %10 = OpTypeFloat 64
+          %7 = OpTypeStruct %6 %10
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  auto uniform_buffer_element_descriptor_f =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  auto uniform_buffer_element_descriptor_d =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+
+  if (std::numeric_limits<float>::has_infinity) {
+    // f == +inf
+    float positive_infinity_float = std::numeric_limits<float>::infinity();
+    uint32_t words[1];
+    memcpy(words, &positive_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+    // f == -inf
+    float negative_infinity_float = std::numeric_limits<float>::infinity();
+    memcpy(words, &negative_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<float>::has_quiet_NaN) {
+    // f == NaN
+    float quiet_nan_float = std::numeric_limits<float>::quiet_NaN();
+    uint32_t words[1];
+    memcpy(words, &quiet_nan_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<double>::has_infinity) {
+    // d == +inf
+    double positive_infinity_double = std::numeric_limits<double>::infinity();
+    uint32_t words[2];
+    memcpy(words, &positive_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+    // d == -inf
+    double negative_infinity_double = -std::numeric_limits<double>::infinity();
+    memcpy(words, &negative_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+
+  if (std::numeric_limits<double>::has_quiet_NaN) {
+    // d == NaN
+    double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
+    uint32_t words[2];
+    memcpy(words, &quiet_nan_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+}
+
+TEST(FactManagerTest, AmbiguousFact) {
+  //  This test came from the following GLSL:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(set = 0, binding = 0) uniform buf {
+  //   float f;
+  // };
+  //
+  // layout(set = 0, binding = 0) uniform buf2 {
+  //   float g;
+  // };
+  //
+  // void main() {
+  //
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpName %9 ""
+               OpName %10 "buf2"
+               OpMemberName %10 0 "g"
+               OpName %12 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  auto uniform_buffer_element_descriptor =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  // The fact cannot be added because it is ambiguous: there are two uniforms
+  // with descriptor set 0 and binding 0.
+  ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {1},
+                             uniform_buffer_element_descriptor));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp
new file mode 100644
index 0000000..e2b9518
--- /dev/null
+++ b/test/fuzz/fuzz_test_util.cpp
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/fuzz/fuzz_test_util.h"
+
+#include <iostream>
+
+namespace spvtools {
+namespace fuzz {
+
+bool IsEqual(const spv_target_env env,
+             const std::vector<uint32_t>& expected_binary,
+             const std::vector<uint32_t>& actual_binary) {
+  if (expected_binary == actual_binary) {
+    return true;
+  }
+  SpirvTools t(env);
+  std::string expected_disassembled;
+  std::string actual_disassembled;
+  if (!t.Disassemble(expected_binary, &expected_disassembled,
+                     kFuzzDisassembleOption)) {
+    return false;
+  }
+  if (!t.Disassemble(actual_binary, &actual_disassembled,
+                     kFuzzDisassembleOption)) {
+    return false;
+  }
+  // Using expect gives us a string diff if the strings are not the same.
+  EXPECT_EQ(expected_disassembled, actual_disassembled);
+  // We then return the result of the equality comparison, to be used by an
+  // assertion in the test root function.
+  return expected_disassembled == actual_disassembled;
+}
+
+bool IsEqual(const spv_target_env env, const std::string& expected_text,
+             const std::vector<uint32_t>& actual_binary) {
+  std::vector<uint32_t> expected_binary;
+  SpirvTools t(env);
+  if (!t.Assemble(expected_text, &expected_binary, kFuzzAssembleOption)) {
+    return false;
+  }
+  return IsEqual(env, expected_binary, actual_binary);
+}
+
+bool IsEqual(const spv_target_env env, const std::string& expected_text,
+             const opt::IRContext* actual_ir) {
+  std::vector<uint32_t> actual_binary;
+  actual_ir->module()->ToBinary(&actual_binary, false);
+  return IsEqual(env, expected_text, actual_binary);
+}
+
+bool IsEqual(const spv_target_env env, const opt::IRContext* ir_1,
+             const opt::IRContext* ir_2) {
+  std::vector<uint32_t> binary_1;
+  ir_1->module()->ToBinary(&binary_1, false);
+  std::vector<uint32_t> binary_2;
+  ir_2->module()->ToBinary(&binary_2, false);
+  return IsEqual(env, binary_1, binary_2);
+}
+
+bool IsValid(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  SpirvTools t(env);
+  return t.Validate(binary);
+}
+
+std::string ToString(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  return ToString(env, binary);
+}
+
+std::string ToString(spv_target_env env, const std::vector<uint32_t>& binary) {
+  SpirvTools t(env);
+  std::string result;
+  t.Disassemble(binary, &result, kFuzzDisassembleOption);
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h
new file mode 100644
index 0000000..88a0b20
--- /dev/null
+++ b/test/fuzz/fuzz_test_util.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef TEST_FUZZ_FUZZ_TEST_UTIL_H_
+#define TEST_FUZZ_FUZZ_TEST_UTIL_H_
+
+#include "gtest/gtest.h"
+
+#include <vector>
+
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Returns true if and only if the given binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const std::vector<uint32_t>& expected_binary,
+             const std::vector<uint32_t>& actual_binary);
+
+// Assembles the given text and returns true if and only if the resulting binary
+// is bit-wise equal to the given binary.
+bool IsEqual(spv_target_env env, const std::string& expected_text,
+             const std::vector<uint32_t>& actual_binary);
+
+// Assembles the given text and turns the given IR into binary, then returns
+// true if and only if the resulting binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const std::string& expected_text,
+             const opt::IRContext* actual_ir);
+
+// Turns the given IRs into binaries, then returns true if and only if the
+// resulting binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const opt::IRContext* ir_1,
+             const opt::IRContext* ir_2);
+
+// Assembles the given IR context and returns true if and only if
+// the resulting binary is valid.
+bool IsValid(spv_target_env env, const opt::IRContext* ir);
+
+// Assembles the given IR context, then returns its disassembly as a string.
+// Useful for debugging.
+std::string ToString(spv_target_env env, const opt::IRContext* ir);
+
+// Returns the disassembly of the given binary as a string.
+// Useful for debugging.
+std::string ToString(spv_target_env env, const std::vector<uint32_t>& binary);
+
+// Assembly options for writing fuzzer tests.  It simplifies matters if
+// numeric ids do not change.
+const uint32_t kFuzzAssembleOption =
+    SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS;
+// Disassembly options for writing fuzzer tests.
+const uint32_t kFuzzDisassembleOption =
+    SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
+
+// A silent message consumer.
+const spvtools::MessageConsumer kSilentConsumer =
+    [](spv_message_level_t, const char*, const spv_position_t&,
+       const char*) -> void {};
+
+const spvtools::MessageConsumer kConsoleMessageConsumer =
+    [](spv_message_level_t level, const char*, const spv_position_t& position,
+       const char* message) -> void {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // TEST_FUZZ_FUZZ_TEST_UTIL_H_
diff --git a/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
new file mode 100644
index 0000000..89f006e
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
@@ -0,0 +1,393 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+bool AddFactHelper(
+    FactManager* fact_manager, opt::IRContext* context, uint32_t word,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  constant_uniform_fact.add_constant_word(word);
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->AddFact(fact, context);
+}
+
+TEST(FuzzerPassAddUsefulConstructsTest, CheckBasicStuffIsAdded) {
+  // The SPIR-V came from the following empty GLSL shader:
+  //
+  // #version 450
+  //
+  // void main()
+  // {
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+                                     &fuzzer_context, &transformation_sequence);
+  pass.Apply();
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeBool
+        %101 = OpTypeInt 32 1
+        %102 = OpTypeInt 32 0
+        %103 = OpTypeFloat 32
+        %104 = OpConstantTrue %100
+        %105 = OpConstantFalse %100
+        %106 = OpConstant %101 0
+        %107 = OpConstant %101 1
+        %108 = OpConstant %102 0
+        %109 = OpConstant %102 1
+        %110 = OpConstant %103 0
+        %111 = OpConstant %103 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+TEST(FuzzerPassAddUsefulConstructsTest,
+     CheckTypesIndicesAndConstantsAddedForUniformFacts) {
+  // The SPIR-V came from the following GLSL shader:
+  //
+  // #version 450
+  //
+  // struct S {
+  //   int x;
+  //   float y;
+  //   int z;
+  //   int w;
+  // };
+  //
+  // uniform buf {
+  //   S s;
+  //   uint w[10];
+  // };
+  //
+  // void main() {
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "S"
+               OpMemberName %8 0 "x"
+               OpMemberName %8 1 "y"
+               OpMemberName %8 2 "z"
+               OpMemberName %8 3 "w"
+               OpName %12 "buf"
+               OpMemberName %12 0 "s"
+               OpMemberName %12 1 "w"
+               OpName %14 ""
+               OpMemberDecorate %8 0 Offset 0
+               OpMemberDecorate %8 1 Offset 4
+               OpMemberDecorate %8 2 Offset 8
+               OpMemberDecorate %8 3 Offset 12
+               OpDecorate %11 ArrayStride 16
+               OpMemberDecorate %12 0 Offset 0
+               OpMemberDecorate %12 1 Offset 16
+               OpDecorate %12 Block
+               OpDecorate %14 DescriptorSet 0
+               OpDecorate %14 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7 %6 %6
+          %9 = OpTypeInt 32 0
+         %10 = OpConstant %9 10
+         %11 = OpTypeArray %9 %10
+         %12 = OpTypeStruct %8 %11
+         %13 = OpTypePointer Uniform %12
+         %14 = OpVariable %13 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  // Add some uniform facts.
+
+  // buf.s.x == 200
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200,
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 0})));
+
+  // buf.s.y == 0.5
+  const float float_value = 0.5;
+  uint32_t float_value_as_uint;
+  memcpy(&float_value_as_uint, &float_value, sizeof(float_value));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_value_as_uint,
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 1})));
+
+  // buf.s.z == 300
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 300,
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 2})));
+
+  // buf.s.w == 400
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 400,
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 3})));
+
+  // buf.w[6] = 22
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22,
+                            MakeUniformBufferElementDescriptor(0, 0, {1, 6})));
+
+  // buf.w[8] = 23
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 23,
+                            MakeUniformBufferElementDescriptor(0, 0, {1, 8})));
+
+  // Assert some things about the module that are not true prior to adding the
+  // pass
+
+  {
+    // No uniform int pointer
+    opt::analysis::Integer temp_type_signed_int(32, true);
+    opt::analysis::Integer* registered_type_signed_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_signed_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_signed_int(
+        registered_type_signed_int, SpvStorageClassUniform);
+    ASSERT_EQ(0,
+              context->get_type_mgr()->GetId(&type_pointer_uniform_signed_int));
+
+    // No uniform uint pointer
+    opt::analysis::Integer temp_type_unsigned_int(32, false);
+    opt::analysis::Integer* registered_type_unsigned_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_unsigned_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_unsigned_int(
+        registered_type_unsigned_int, SpvStorageClassUniform);
+    ASSERT_EQ(
+        0, context->get_type_mgr()->GetId(&type_pointer_uniform_unsigned_int));
+
+    // No uniform float pointer
+    opt::analysis::Float temp_type_float(32);
+    opt::analysis::Float* registered_type_float =
+        context->get_type_mgr()->GetRegisteredType(&temp_type_float)->AsFloat();
+    opt::analysis::Pointer type_pointer_uniform_float(registered_type_float,
+                                                      SpvStorageClassUniform);
+    ASSERT_EQ(0, context->get_type_mgr()->GetId(&type_pointer_uniform_float));
+
+    // No int constants 200, 300 nor 400
+    opt::analysis::IntConstant int_constant_200(registered_type_signed_int,
+                                                {200});
+    opt::analysis::IntConstant int_constant_300(registered_type_signed_int,
+                                                {300});
+    opt::analysis::IntConstant int_constant_400(registered_type_signed_int,
+                                                {400});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_200));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_300));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_400));
+
+    // No float constant 0.5
+    opt::analysis::FloatConstant float_constant_zero_point_five(
+        registered_type_float, {float_value_as_uint});
+    ASSERT_EQ(nullptr, context->get_constant_mgr()->FindConstant(
+                           &float_constant_zero_point_five));
+
+    // No uint constant 22
+    opt::analysis::IntConstant uint_constant_22(registered_type_unsigned_int,
+                                                {22});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_22));
+
+    // No uint constant 23
+    opt::analysis::IntConstant uint_constant_23(registered_type_unsigned_int,
+                                                {23});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_23));
+
+    // No int constants 0, 1, 2, 3, 6, 8
+    opt::analysis::IntConstant int_constant_0(registered_type_signed_int, {0});
+    opt::analysis::IntConstant int_constant_1(registered_type_signed_int, {1});
+    opt::analysis::IntConstant int_constant_2(registered_type_signed_int, {2});
+    opt::analysis::IntConstant int_constant_3(registered_type_signed_int, {3});
+    opt::analysis::IntConstant int_constant_6(registered_type_signed_int, {6});
+    opt::analysis::IntConstant int_constant_8(registered_type_signed_int, {8});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_0));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_1));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_2));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_3));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_6));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_8));
+  }
+
+  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+                                     &fuzzer_context, &transformation_sequence);
+  pass.Apply();
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Now assert some things about the module that should be true following the
+  // pass.
+
+  // We reconstruct all necessary types and constants to guard against the type
+  // and constant managers for the module having been invalidated.
+
+  {
+    // Uniform int pointer now present
+    opt::analysis::Integer temp_type_signed_int(32, true);
+    opt::analysis::Integer* registered_type_signed_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_signed_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_signed_int(
+        registered_type_signed_int, SpvStorageClassUniform);
+    ASSERT_NE(0,
+              context->get_type_mgr()->GetId(&type_pointer_uniform_signed_int));
+
+    // Uniform uint pointer now present
+    opt::analysis::Integer temp_type_unsigned_int(32, false);
+    opt::analysis::Integer* registered_type_unsigned_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_unsigned_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_unsigned_int(
+        registered_type_unsigned_int, SpvStorageClassUniform);
+    ASSERT_NE(
+        0, context->get_type_mgr()->GetId(&type_pointer_uniform_unsigned_int));
+
+    // Uniform float pointer now present
+    opt::analysis::Float temp_type_float(32);
+    opt::analysis::Float* registered_type_float =
+        context->get_type_mgr()->GetRegisteredType(&temp_type_float)->AsFloat();
+    opt::analysis::Pointer type_pointer_uniform_float(registered_type_float,
+                                                      SpvStorageClassUniform);
+    ASSERT_NE(0, context->get_type_mgr()->GetId(&type_pointer_uniform_float));
+
+    // int constants 200, 300, 400 now present
+    opt::analysis::IntConstant int_constant_200(registered_type_signed_int,
+                                                {200});
+    opt::analysis::IntConstant int_constant_300(registered_type_signed_int,
+                                                {300});
+    opt::analysis::IntConstant int_constant_400(registered_type_signed_int,
+                                                {400});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_200));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_300));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_400));
+
+    // float constant 0.5 now present
+    opt::analysis::FloatConstant float_constant_zero_point_five(
+        registered_type_float, {float_value_as_uint});
+    ASSERT_NE(nullptr, context->get_constant_mgr()->FindConstant(
+                           &float_constant_zero_point_five));
+
+    // uint constant 22 now present
+    opt::analysis::IntConstant uint_constant_22(registered_type_unsigned_int,
+                                                {22});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_22));
+
+    // uint constant 23 now present
+    opt::analysis::IntConstant uint_constant_23(registered_type_unsigned_int,
+                                                {23});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_23));
+
+    // int constants 0, 1, 2, 3, 6, 8 now present
+    opt::analysis::IntConstant int_constant_0(registered_type_signed_int, {0});
+    opt::analysis::IntConstant int_constant_1(registered_type_signed_int, {1});
+    opt::analysis::IntConstant int_constant_2(registered_type_signed_int, {2});
+    opt::analysis::IntConstant int_constant_3(registered_type_signed_int, {3});
+    opt::analysis::IntConstant int_constant_6(registered_type_signed_int, {6});
+    opt::analysis::IntConstant int_constant_8(registered_type_signed_int, {8});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_0));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_1));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_2));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_3));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_6));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_8));
+  }
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
new file mode 100644
index 0000000..550c4fc
--- /dev/null
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -0,0 +1,980 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/replayer.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// Assembles the given |shader| text, and then runs the fuzzer |num_runs|
+// times, using successive seeds starting from |initial_seed|.  Checks that
+// the binary produced after each fuzzer run is valid, and that replaying
+// the transformations that were applied during fuzzing leads to an
+// identical binary.
+void RunFuzzerAndReplayer(const std::string& shader,
+                          const protobufs::FactSequence& initial_facts,
+                          uint32_t initial_seed, uint32_t num_runs) {
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+  t.SetMessageConsumer(kConsoleMessageConsumer);
+  ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
+  ASSERT_TRUE(t.Validate(binary_in));
+
+  for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
+    std::vector<uint32_t> fuzzer_binary_out;
+    protobufs::TransformationSequence fuzzer_transformation_sequence_out;
+    spvtools::FuzzerOptions fuzzer_options;
+    spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
+
+    Fuzzer fuzzer(env);
+    fuzzer.SetMessageConsumer(kSilentConsumer);
+    auto fuzzer_result_status =
+        fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out,
+                   &fuzzer_transformation_sequence_out);
+    ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
+    ASSERT_TRUE(t.Validate(fuzzer_binary_out));
+
+    std::vector<uint32_t> replayer_binary_out;
+    protobufs::TransformationSequence replayer_transformation_sequence_out;
+
+    Replayer replayer(env);
+    replayer.SetMessageConsumer(kSilentConsumer);
+    auto replayer_result_status = replayer.Run(
+        binary_in, initial_facts, fuzzer_transformation_sequence_out,
+        &replayer_binary_out, &replayer_transformation_sequence_out);
+    ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
+              replayer_result_status);
+
+    // After replaying the transformations applied by the fuzzer, exactly those
+    // transformations should have been applied, and the binary resulting from
+    // replay should be identical to that which resulted from fuzzing.
+    std::string fuzzer_transformations_string;
+    std::string replayer_transformations_string;
+    fuzzer_transformation_sequence_out.SerializeToString(
+        &fuzzer_transformations_string);
+    replayer_transformation_sequence_out.SerializeToString(
+        &replayer_transformations_string);
+    ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
+    ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
+  }
+}
+
+TEST(FuzzerReplayerTest, Miscellaneous1) {
+  // The SPIR-V came from this GLSL:
+  //
+  // #version 310 es
+  //
+  // void foo() {
+  //   int x;
+  //   x = 2;
+  //   for (int i = 0; i < 100; i++) {
+  //     x += i;
+  //     x = x * 2;
+  //   }
+  //   return;
+  // }
+  //
+  // void main() {
+  //   foo();
+  //   for (int i = 0; i < 10; i++) {
+  //     int j = 20;
+  //     while(j > 0) {
+  //       foo();
+  //       j--;
+  //     }
+  //     do {
+  //       i++;
+  //     } while(i < 4);
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "foo("
+               OpName %10 "x"
+               OpName %12 "i"
+               OpName %33 "i"
+               OpName %42 "j"
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %33 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %49 RelaxedPrecision
+               OpDecorate %52 RelaxedPrecision
+               OpDecorate %53 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %59 RelaxedPrecision
+               OpDecorate %60 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %64 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 2
+         %13 = OpConstant %8 0
+         %20 = OpConstant %8 100
+         %21 = OpTypeBool
+         %29 = OpConstant %8 1
+         %40 = OpConstant %8 10
+         %43 = OpConstant %8 20
+         %61 = OpConstant %8 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %33 = OpVariable %9 Function
+         %42 = OpVariable %9 Function
+         %32 = OpFunctionCall %2 %6
+               OpStore %33 %13
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %39 = OpLoad %8 %33
+         %41 = OpSLessThan %21 %39 %40
+               OpBranchConditional %41 %35 %36
+         %35 = OpLabel
+               OpStore %42 %43
+               OpBranch %44
+         %44 = OpLabel
+               OpLoopMerge %46 %47 None
+               OpBranch %48
+         %48 = OpLabel
+         %49 = OpLoad %8 %42
+         %50 = OpSGreaterThan %21 %49 %13
+               OpBranchConditional %50 %45 %46
+         %45 = OpLabel
+         %51 = OpFunctionCall %2 %6
+         %52 = OpLoad %8 %42
+         %53 = OpISub %8 %52 %29
+               OpStore %42 %53
+               OpBranch %47
+         %47 = OpLabel
+               OpBranch %44
+         %46 = OpLabel
+               OpBranch %54
+         %54 = OpLabel
+               OpLoopMerge %56 %57 None
+               OpBranch %55
+         %55 = OpLabel
+         %58 = OpLoad %8 %33
+         %59 = OpIAdd %8 %58 %29
+               OpStore %33 %59
+               OpBranch %57
+         %57 = OpLabel
+         %60 = OpLoad %8 %33
+         %62 = OpSLessThan %21 %60 %61
+               OpBranchConditional %62 %54 %56
+         %56 = OpLabel
+               OpBranch %37
+         %37 = OpLabel
+         %63 = OpLoad %8 %33
+         %64 = OpIAdd %8 %63 %29
+               OpStore %33 %64
+               OpBranch %34
+         %36 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+         %12 = OpVariable %9 Function
+               OpStore %10 %11
+               OpStore %12 %13
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpLoad %8 %12
+         %22 = OpSLessThan %21 %19 %20
+               OpBranchConditional %22 %15 %16
+         %15 = OpLabel
+         %23 = OpLoad %8 %12
+         %24 = OpLoad %8 %10
+         %25 = OpIAdd %8 %24 %23
+               OpStore %10 %25
+         %26 = OpLoad %8 %10
+         %27 = OpIMul %8 %26 %11
+               OpStore %10 %27
+               OpBranch %17
+         %17 = OpLabel
+         %28 = OpLoad %8 %12
+         %30 = OpIAdd %8 %28 %29
+               OpStore %12 %30
+               OpBranch %14
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Do 5 fuzzer runs, starting from an initial seed of 0 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 5);
+}
+
+TEST(FuzzerReplayerTest, Miscellaneous2) {
+  // The SPIR-V came from this GLSL, which was then optimized using spirv-opt
+  // with the -O argument:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(location = 0) out vec4 _GLF_color;
+  //
+  // layout(set = 0, binding = 0) uniform buf0 {
+  //  vec2 injectionSwitch;
+  // };
+  // layout(set = 0, binding = 1) uniform buf1 {
+  //  vec2 resolution;
+  // };
+  // bool checkSwap(float a, float b)
+  // {
+  //  return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
+  // }
+  // void main()
+  // {
+  //  float data[10];
+  //  for(int i = 0; i < 10; i++)
+  //   {
+  //    data[i] = float(10 - i) * injectionSwitch.y;
+  //   }
+  //  for(int i = 0; i < 9; i++)
+  //   {
+  //    for(int j = 0; j < 10; j++)
+  //     {
+  //      if(j < i + 1)
+  //       {
+  //        continue;
+  //       }
+  //      bool doSwap = checkSwap(data[i], data[j]);
+  //      if(doSwap)
+  //       {
+  //        float temp = data[i];
+  //        data[i] = data[j];
+  //        data[j] = temp;
+  //       }
+  //     }
+  //   }
+  //  if(gl_FragCoord.x < resolution.x / 2.0)
+  //   {
+  //    _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
+  //   }
+  //  else
+  //   {
+  //    _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %16 %139
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %16 "gl_FragCoord"
+               OpName %23 "buf1"
+               OpMemberName %23 0 "resolution"
+               OpName %25 ""
+               OpName %61 "data"
+               OpName %66 "buf0"
+               OpMemberName %66 0 "injectionSwitch"
+               OpName %68 ""
+               OpName %139 "_GLF_color"
+               OpDecorate %16 BuiltIn FragCoord
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 Block
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+               OpDecorate %64 RelaxedPrecision
+               OpMemberDecorate %66 0 Offset 0
+               OpDecorate %66 Block
+               OpDecorate %68 DescriptorSet 0
+               OpDecorate %68 Binding 0
+               OpDecorate %75 RelaxedPrecision
+               OpDecorate %95 RelaxedPrecision
+               OpDecorate %126 RelaxedPrecision
+               OpDecorate %128 RelaxedPrecision
+               OpDecorate %139 Location 0
+               OpDecorate %182 RelaxedPrecision
+               OpDecorate %183 RelaxedPrecision
+               OpDecorate %184 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeBool
+         %14 = OpTypeVector %6 4
+         %15 = OpTypePointer Input %14
+         %16 = OpVariable %15 Input
+         %17 = OpTypeInt 32 0
+         %18 = OpConstant %17 1
+         %19 = OpTypePointer Input %6
+         %22 = OpTypeVector %6 2
+         %23 = OpTypeStruct %22
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %26 = OpTypeInt 32 1
+         %27 = OpConstant %26 0
+         %28 = OpTypePointer Uniform %6
+         %56 = OpConstant %26 10
+         %58 = OpConstant %17 10
+         %59 = OpTypeArray %6 %58
+         %60 = OpTypePointer Function %59
+         %66 = OpTypeStruct %22
+         %67 = OpTypePointer Uniform %66
+         %68 = OpVariable %67 Uniform
+         %74 = OpConstant %26 1
+         %83 = OpConstant %26 9
+        %129 = OpConstant %17 0
+        %138 = OpTypePointer Output %14
+        %139 = OpVariable %138 Output
+        %144 = OpConstant %26 5
+        %151 = OpConstant %6 1
+        %194 = OpConstant %6 0.5
+        %195 = OpConstant %6 0.100000001
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %61 = OpVariable %60 Function
+               OpBranch %50
+         %50 = OpLabel
+        %182 = OpPhi %26 %27 %5 %75 %51
+         %57 = OpSLessThan %8 %182 %56
+               OpLoopMerge %52 %51 None
+               OpBranchConditional %57 %51 %52
+         %51 = OpLabel
+         %64 = OpISub %26 %56 %182
+         %65 = OpConvertSToF %6 %64
+         %69 = OpAccessChain %28 %68 %27 %18
+         %70 = OpLoad %6 %69
+         %71 = OpFMul %6 %65 %70
+         %72 = OpAccessChain %7 %61 %182
+               OpStore %72 %71
+         %75 = OpIAdd %26 %182 %74
+               OpBranch %50
+         %52 = OpLabel
+               OpBranch %77
+         %77 = OpLabel
+        %183 = OpPhi %26 %27 %52 %128 %88
+         %84 = OpSLessThan %8 %183 %83
+               OpLoopMerge %79 %88 None
+               OpBranchConditional %84 %78 %79
+         %78 = OpLabel
+               OpBranch %86
+         %86 = OpLabel
+        %184 = OpPhi %26 %27 %78 %126 %89
+         %92 = OpSLessThan %8 %184 %56
+               OpLoopMerge %88 %89 None
+               OpBranchConditional %92 %87 %88
+         %87 = OpLabel
+         %95 = OpIAdd %26 %183 %74
+         %96 = OpSLessThan %8 %184 %95
+               OpSelectionMerge %98 None
+               OpBranchConditional %96 %97 %98
+         %97 = OpLabel
+               OpBranch %89
+         %98 = OpLabel
+        %104 = OpAccessChain %7 %61 %183
+        %105 = OpLoad %6 %104
+        %107 = OpAccessChain %7 %61 %184
+        %108 = OpLoad %6 %107
+        %166 = OpAccessChain %19 %16 %18
+        %167 = OpLoad %6 %166
+        %168 = OpAccessChain %28 %25 %27 %18
+        %169 = OpLoad %6 %168
+        %170 = OpFMul %6 %169 %194
+        %171 = OpFOrdLessThan %8 %167 %170
+               OpSelectionMerge %172 None
+               OpBranchConditional %171 %173 %174
+        %173 = OpLabel
+        %177 = OpFOrdGreaterThan %8 %105 %108
+               OpBranch %172
+        %174 = OpLabel
+        %180 = OpFOrdLessThan %8 %105 %108
+               OpBranch %172
+        %172 = OpLabel
+        %186 = OpPhi %8 %177 %173 %180 %174
+               OpSelectionMerge %112 None
+               OpBranchConditional %186 %111 %112
+        %111 = OpLabel
+        %116 = OpLoad %6 %104
+        %120 = OpLoad %6 %107
+               OpStore %104 %120
+               OpStore %107 %116
+               OpBranch %112
+        %112 = OpLabel
+               OpBranch %89
+         %89 = OpLabel
+        %126 = OpIAdd %26 %184 %74
+               OpBranch %86
+         %88 = OpLabel
+        %128 = OpIAdd %26 %183 %74
+               OpBranch %77
+         %79 = OpLabel
+        %130 = OpAccessChain %19 %16 %129
+        %131 = OpLoad %6 %130
+        %132 = OpAccessChain %28 %25 %27 %129
+        %133 = OpLoad %6 %132
+        %134 = OpFMul %6 %133 %194
+        %135 = OpFOrdLessThan %8 %131 %134
+               OpSelectionMerge %137 None
+               OpBranchConditional %135 %136 %153
+        %136 = OpLabel
+        %140 = OpAccessChain %7 %61 %27
+        %141 = OpLoad %6 %140
+        %143 = OpFMul %6 %141 %195
+        %145 = OpAccessChain %7 %61 %144
+        %146 = OpLoad %6 %145
+        %147 = OpFMul %6 %146 %195
+        %148 = OpAccessChain %7 %61 %83
+        %149 = OpLoad %6 %148
+        %150 = OpFMul %6 %149 %195
+        %152 = OpCompositeConstruct %14 %143 %147 %150 %151
+               OpStore %139 %152
+               OpBranch %137
+        %153 = OpLabel
+        %154 = OpAccessChain %7 %61 %144
+        %155 = OpLoad %6 %154
+        %156 = OpFMul %6 %155 %195
+        %157 = OpAccessChain %7 %61 %83
+        %158 = OpLoad %6 %157
+        %159 = OpFMul %6 %158 %195
+        %160 = OpAccessChain %7 %61 %27
+        %161 = OpLoad %6 %160
+        %162 = OpFMul %6 %161 %195
+        %163 = OpCompositeConstruct %14 %156 %159 %162 %151
+               OpStore %139 %163
+               OpBranch %137
+        %137 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Do 5 fuzzer runs, starting from an initial seed of 10 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 5);
+}
+
+TEST(FuzzerReplayerTest, Miscellaneous3) {
+  // The SPIR-V came from this GLSL, which was then optimized using spirv-opt
+  // with the -O argument:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(location = 0) out vec4 _GLF_color;
+  //
+  // layout(set = 0, binding = 0) uniform buf0 {
+  //  vec2 resolution;
+  // };
+  // void main(void)
+  // {
+  //  float A[50];
+  //  for(
+  //      int i = 0;
+  //      i < 200;
+  //      i ++
+  //  )
+  //   {
+  //    if(i >= int(resolution.x))
+  //     {
+  //      break;
+  //     }
+  //    if((4 * (i / 4)) == i)
+  //     {
+  //      A[i / 4] = float(i);
+  //     }
+  //   }
+  //  for(
+  //      int i = 0;
+  //      i < 50;
+  //      i ++
+  //  )
+  //   {
+  //    if(i < int(gl_FragCoord.x))
+  //     {
+  //      break;
+  //     }
+  //    if(i > 0)
+  //     {
+  //      A[i] += A[i - 1];
+  //     }
+  //   }
+  //  if(int(gl_FragCoord.x) < 20)
+  //   {
+  //    _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
+  //   }
+  //  else
+  //   if(int(gl_FragCoord.x) < 40)
+  //    {
+  //     _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
+  //    }
+  //   else
+  //    if(int(gl_FragCoord.x) < 60)
+  //     {
+  //      _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
+  //      1.0, 1.0);
+  //     }
+  //    else
+  //     if(int(gl_FragCoord.x) < 80)
+  //      {
+  //       _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
+  //       1.0, 1.0);
+  //      }
+  //     else
+  //      if(int(gl_FragCoord.x) < 100)
+  //       {
+  //        _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
+  //        1.0, 1.0);
+  //       }
+  //      else
+  //       if(int(gl_FragCoord.x) < 120)
+  //        {
+  //         _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
+  //         1.0, 1.0);
+  //        }
+  //       else
+  //        if(int(gl_FragCoord.x) < 140)
+  //         {
+  //          _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
+  //          1.0, 1.0);
+  //         }
+  //        else
+  //         if(int(gl_FragCoord.x) < 160)
+  //          {
+  //           _GLF_color = vec4(A[35] / resolution.x, A[39] /
+  //           resolution.y, 1.0, 1.0);
+  //          }
+  //         else
+  //          if(int(gl_FragCoord.x) < 180)
+  //           {
+  //            _GLF_color = vec4(A[40] / resolution.x, A[44] /
+  //            resolution.y, 1.0, 1.0);
+  //           }
+  //          else
+  //           if(int(gl_FragCoord.x) < 180)
+  //            {
+  //             _GLF_color = vec4(A[45] / resolution.x, A[49] /
+  //             resolution.y, 1.0, 1.0);
+  //            }
+  //           else
+  //            {
+  //             discard;
+  //            }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %68 %100
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %22 "buf0"
+               OpMemberName %22 0 "resolution"
+               OpName %24 ""
+               OpName %46 "A"
+               OpName %68 "gl_FragCoord"
+               OpName %100 "_GLF_color"
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %68 BuiltIn FragCoord
+               OpDecorate %83 RelaxedPrecision
+               OpDecorate %91 RelaxedPrecision
+               OpDecorate %100 Location 0
+               OpDecorate %302 RelaxedPrecision
+               OpDecorate %304 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 200
+         %17 = OpTypeBool
+         %20 = OpTypeFloat 32
+         %21 = OpTypeVector %20 2
+         %22 = OpTypeStruct %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypeInt 32 0
+         %26 = OpConstant %25 0
+         %27 = OpTypePointer Uniform %20
+         %35 = OpConstant %6 4
+         %43 = OpConstant %25 50
+         %44 = OpTypeArray %20 %43
+         %45 = OpTypePointer Function %44
+         %51 = OpTypePointer Function %20
+         %54 = OpConstant %6 1
+         %63 = OpConstant %6 50
+         %66 = OpTypeVector %20 4
+         %67 = OpTypePointer Input %66
+         %68 = OpVariable %67 Input
+         %69 = OpTypePointer Input %20
+         %95 = OpConstant %6 20
+         %99 = OpTypePointer Output %66
+        %100 = OpVariable %99 Output
+        %108 = OpConstant %25 1
+        %112 = OpConstant %20 1
+        %118 = OpConstant %6 40
+        %122 = OpConstant %6 5
+        %128 = OpConstant %6 9
+        %139 = OpConstant %6 60
+        %143 = OpConstant %6 10
+        %149 = OpConstant %6 14
+        %160 = OpConstant %6 80
+        %164 = OpConstant %6 15
+        %170 = OpConstant %6 19
+        %181 = OpConstant %6 100
+        %190 = OpConstant %6 24
+        %201 = OpConstant %6 120
+        %205 = OpConstant %6 25
+        %211 = OpConstant %6 29
+        %222 = OpConstant %6 140
+        %226 = OpConstant %6 30
+        %232 = OpConstant %6 34
+        %243 = OpConstant %6 160
+        %247 = OpConstant %6 35
+        %253 = OpConstant %6 39
+        %264 = OpConstant %6 180
+        %273 = OpConstant %6 44
+        %287 = OpConstant %6 45
+        %293 = OpConstant %6 49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %46 = OpVariable %45 Function
+               OpBranch %10
+         %10 = OpLabel
+        %302 = OpPhi %6 %9 %5 %55 %42
+         %18 = OpSLessThan %17 %302 %16
+               OpLoopMerge %12 %42 None
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %28 = OpAccessChain %27 %24 %9 %26
+         %29 = OpLoad %20 %28
+         %30 = OpConvertFToS %6 %29
+         %31 = OpSGreaterThanEqual %17 %302 %30
+               OpSelectionMerge %33 None
+               OpBranchConditional %31 %32 %33
+         %32 = OpLabel
+               OpBranch %12
+         %33 = OpLabel
+         %37 = OpSDiv %6 %302 %35
+         %38 = OpIMul %6 %35 %37
+         %40 = OpIEqual %17 %38 %302
+               OpSelectionMerge %42 None
+               OpBranchConditional %40 %41 %42
+         %41 = OpLabel
+         %50 = OpConvertSToF %20 %302
+         %52 = OpAccessChain %51 %46 %37
+               OpStore %52 %50
+               OpBranch %42
+         %42 = OpLabel
+         %55 = OpIAdd %6 %302 %54
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %57
+         %57 = OpLabel
+        %304 = OpPhi %6 %9 %12 %91 %80
+         %64 = OpSLessThan %17 %304 %63
+               OpLoopMerge %59 %80 None
+               OpBranchConditional %64 %58 %59
+         %58 = OpLabel
+         %70 = OpAccessChain %69 %68 %26
+         %71 = OpLoad %20 %70
+         %72 = OpConvertFToS %6 %71
+         %73 = OpSLessThan %17 %304 %72
+               OpSelectionMerge %75 None
+               OpBranchConditional %73 %74 %75
+         %74 = OpLabel
+               OpBranch %59
+         %75 = OpLabel
+         %78 = OpSGreaterThan %17 %304 %9
+               OpSelectionMerge %80 None
+               OpBranchConditional %78 %79 %80
+         %79 = OpLabel
+         %83 = OpISub %6 %304 %54
+         %84 = OpAccessChain %51 %46 %83
+         %85 = OpLoad %20 %84
+         %86 = OpAccessChain %51 %46 %304
+         %87 = OpLoad %20 %86
+         %88 = OpFAdd %20 %87 %85
+               OpStore %86 %88
+               OpBranch %80
+         %80 = OpLabel
+         %91 = OpIAdd %6 %304 %54
+               OpBranch %57
+         %59 = OpLabel
+         %92 = OpAccessChain %69 %68 %26
+         %93 = OpLoad %20 %92
+         %94 = OpConvertFToS %6 %93
+         %96 = OpSLessThan %17 %94 %95
+               OpSelectionMerge %98 None
+               OpBranchConditional %96 %97 %114
+         %97 = OpLabel
+        %101 = OpAccessChain %51 %46 %9
+        %102 = OpLoad %20 %101
+        %103 = OpAccessChain %27 %24 %9 %26
+        %104 = OpLoad %20 %103
+        %105 = OpFDiv %20 %102 %104
+        %106 = OpAccessChain %51 %46 %35
+        %107 = OpLoad %20 %106
+        %109 = OpAccessChain %27 %24 %9 %108
+        %110 = OpLoad %20 %109
+        %111 = OpFDiv %20 %107 %110
+        %113 = OpCompositeConstruct %66 %105 %111 %112 %112
+               OpStore %100 %113
+               OpBranch %98
+        %114 = OpLabel
+        %119 = OpSLessThan %17 %94 %118
+               OpSelectionMerge %121 None
+               OpBranchConditional %119 %120 %135
+        %120 = OpLabel
+        %123 = OpAccessChain %51 %46 %122
+        %124 = OpLoad %20 %123
+        %125 = OpAccessChain %27 %24 %9 %26
+        %126 = OpLoad %20 %125
+        %127 = OpFDiv %20 %124 %126
+        %129 = OpAccessChain %51 %46 %128
+        %130 = OpLoad %20 %129
+        %131 = OpAccessChain %27 %24 %9 %108
+        %132 = OpLoad %20 %131
+        %133 = OpFDiv %20 %130 %132
+        %134 = OpCompositeConstruct %66 %127 %133 %112 %112
+               OpStore %100 %134
+               OpBranch %121
+        %135 = OpLabel
+        %140 = OpSLessThan %17 %94 %139
+               OpSelectionMerge %142 None
+               OpBranchConditional %140 %141 %156
+        %141 = OpLabel
+        %144 = OpAccessChain %51 %46 %143
+        %145 = OpLoad %20 %144
+        %146 = OpAccessChain %27 %24 %9 %26
+        %147 = OpLoad %20 %146
+        %148 = OpFDiv %20 %145 %147
+        %150 = OpAccessChain %51 %46 %149
+        %151 = OpLoad %20 %150
+        %152 = OpAccessChain %27 %24 %9 %108
+        %153 = OpLoad %20 %152
+        %154 = OpFDiv %20 %151 %153
+        %155 = OpCompositeConstruct %66 %148 %154 %112 %112
+               OpStore %100 %155
+               OpBranch %142
+        %156 = OpLabel
+        %161 = OpSLessThan %17 %94 %160
+               OpSelectionMerge %163 None
+               OpBranchConditional %161 %162 %177
+        %162 = OpLabel
+        %165 = OpAccessChain %51 %46 %164
+        %166 = OpLoad %20 %165
+        %167 = OpAccessChain %27 %24 %9 %26
+        %168 = OpLoad %20 %167
+        %169 = OpFDiv %20 %166 %168
+        %171 = OpAccessChain %51 %46 %170
+        %172 = OpLoad %20 %171
+        %173 = OpAccessChain %27 %24 %9 %108
+        %174 = OpLoad %20 %173
+        %175 = OpFDiv %20 %172 %174
+        %176 = OpCompositeConstruct %66 %169 %175 %112 %112
+               OpStore %100 %176
+               OpBranch %163
+        %177 = OpLabel
+        %182 = OpSLessThan %17 %94 %181
+               OpSelectionMerge %184 None
+               OpBranchConditional %182 %183 %197
+        %183 = OpLabel
+        %185 = OpAccessChain %51 %46 %95
+        %186 = OpLoad %20 %185
+        %187 = OpAccessChain %27 %24 %9 %26
+        %188 = OpLoad %20 %187
+        %189 = OpFDiv %20 %186 %188
+        %191 = OpAccessChain %51 %46 %190
+        %192 = OpLoad %20 %191
+        %193 = OpAccessChain %27 %24 %9 %108
+        %194 = OpLoad %20 %193
+        %195 = OpFDiv %20 %192 %194
+        %196 = OpCompositeConstruct %66 %189 %195 %112 %112
+               OpStore %100 %196
+               OpBranch %184
+        %197 = OpLabel
+        %202 = OpSLessThan %17 %94 %201
+               OpSelectionMerge %204 None
+               OpBranchConditional %202 %203 %218
+        %203 = OpLabel
+        %206 = OpAccessChain %51 %46 %205
+        %207 = OpLoad %20 %206
+        %208 = OpAccessChain %27 %24 %9 %26
+        %209 = OpLoad %20 %208
+        %210 = OpFDiv %20 %207 %209
+        %212 = OpAccessChain %51 %46 %211
+        %213 = OpLoad %20 %212
+        %214 = OpAccessChain %27 %24 %9 %108
+        %215 = OpLoad %20 %214
+        %216 = OpFDiv %20 %213 %215
+        %217 = OpCompositeConstruct %66 %210 %216 %112 %112
+               OpStore %100 %217
+               OpBranch %204
+        %218 = OpLabel
+        %223 = OpSLessThan %17 %94 %222
+               OpSelectionMerge %225 None
+               OpBranchConditional %223 %224 %239
+        %224 = OpLabel
+        %227 = OpAccessChain %51 %46 %226
+        %228 = OpLoad %20 %227
+        %229 = OpAccessChain %27 %24 %9 %26
+        %230 = OpLoad %20 %229
+        %231 = OpFDiv %20 %228 %230
+        %233 = OpAccessChain %51 %46 %232
+        %234 = OpLoad %20 %233
+        %235 = OpAccessChain %27 %24 %9 %108
+        %236 = OpLoad %20 %235
+        %237 = OpFDiv %20 %234 %236
+        %238 = OpCompositeConstruct %66 %231 %237 %112 %112
+               OpStore %100 %238
+               OpBranch %225
+        %239 = OpLabel
+        %244 = OpSLessThan %17 %94 %243
+               OpSelectionMerge %246 None
+               OpBranchConditional %244 %245 %260
+        %245 = OpLabel
+        %248 = OpAccessChain %51 %46 %247
+        %249 = OpLoad %20 %248
+        %250 = OpAccessChain %27 %24 %9 %26
+        %251 = OpLoad %20 %250
+        %252 = OpFDiv %20 %249 %251
+        %254 = OpAccessChain %51 %46 %253
+        %255 = OpLoad %20 %254
+        %256 = OpAccessChain %27 %24 %9 %108
+        %257 = OpLoad %20 %256
+        %258 = OpFDiv %20 %255 %257
+        %259 = OpCompositeConstruct %66 %252 %258 %112 %112
+               OpStore %100 %259
+               OpBranch %246
+        %260 = OpLabel
+        %265 = OpSLessThan %17 %94 %264
+               OpSelectionMerge %267 None
+               OpBranchConditional %265 %266 %280
+        %266 = OpLabel
+        %268 = OpAccessChain %51 %46 %118
+        %269 = OpLoad %20 %268
+        %270 = OpAccessChain %27 %24 %9 %26
+        %271 = OpLoad %20 %270
+        %272 = OpFDiv %20 %269 %271
+        %274 = OpAccessChain %51 %46 %273
+        %275 = OpLoad %20 %274
+        %276 = OpAccessChain %27 %24 %9 %108
+        %277 = OpLoad %20 %276
+        %278 = OpFDiv %20 %275 %277
+        %279 = OpCompositeConstruct %66 %272 %278 %112 %112
+               OpStore %100 %279
+               OpBranch %267
+        %280 = OpLabel
+               OpSelectionMerge %285 None
+               OpBranchConditional %265 %285 %300
+        %285 = OpLabel
+        %288 = OpAccessChain %51 %46 %287
+        %289 = OpLoad %20 %288
+        %290 = OpAccessChain %27 %24 %9 %26
+        %291 = OpLoad %20 %290
+        %292 = OpFDiv %20 %289 %291
+        %294 = OpAccessChain %51 %46 %293
+        %295 = OpLoad %20 %294
+        %296 = OpAccessChain %27 %24 %9 %108
+        %297 = OpLoad %20 %296
+        %298 = OpFDiv %20 %295 %297
+        %299 = OpCompositeConstruct %66 %292 %298 %112 %112
+               OpStore %100 %299
+               OpBranch %267
+        %300 = OpLabel
+               OpKill
+        %267 = OpLabel
+               OpBranch %246
+        %246 = OpLabel
+               OpBranch %225
+        %225 = OpLabel
+               OpBranch %204
+        %204 = OpLabel
+               OpBranch %184
+        %184 = OpLabel
+               OpBranch %163
+        %163 = OpLabel
+               OpBranch %142
+        %142 = OpLabel
+               OpBranch %121
+        %121 = OpLabel
+               OpBranch %98
+         %98 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Add the facts "resolution.x == 250" and "resolution.y == 100".
+  protobufs::FactSequence facts;
+  {
+    protobufs::FactConstantUniform resolution_x_eq_250;
+    *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(0, 0, {0, 0});
+    *resolution_x_eq_250.mutable_constant_word()->Add() = 250;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_x_eq_250;
+    *facts.mutable_fact()->Add() = temp;
+  }
+  {
+    protobufs::FactConstantUniform resolution_y_eq_100;
+    *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(0, 0, {0, 1});
+    *resolution_y_eq_100.mutable_constant_word()->Add() = 100;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_y_eq_100;
+    *facts.mutable_fact()->Add() = temp;
+  }
+
+  // Do 5 fuzzer runs, starting from an initial seed of 94 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(shader, facts, 94, 5);
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
new file mode 100644
index 0000000..85f41bc
--- /dev/null
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -0,0 +1,1108 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <functional>
+#include <vector>
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/shrinker.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// Abstract class exposing an interestingness function as a virtual method.
+class InterestingnessTest {
+ public:
+  virtual ~InterestingnessTest() = default;
+
+  // Abstract method that subclasses should implement for specific notions of
+  // interestingness. Its signature matches Shrinker::InterestingnessFunction.
+  // Argument |binary| is the SPIR-V binary to be checked; |counter| is used for
+  // debugging purposes.
+  virtual bool Interesting(const std::vector<uint32_t>& binary,
+                           uint32_t counter) = 0;
+
+  // Yields the Interesting instance method wrapped in a function object.
+  Shrinker::InterestingnessFunction AsFunction() {
+    return std::bind(&InterestingnessTest::Interesting, this,
+                     std::placeholders::_1, std::placeholders::_2);
+  }
+};
+
+// A test that says all binaries are interesting.
+class AlwaysInteresting : public InterestingnessTest {
+ public:
+  bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+    return true;
+  }
+};
+
+// A test that says a binary is interesting first time round, and uninteresting
+// thereafter.
+class OnlyInterestingFirstTime : public InterestingnessTest {
+ public:
+  explicit OnlyInterestingFirstTime() : first_time_(true) {}
+
+  bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+    if (first_time_) {
+      first_time_ = false;
+      return true;
+    }
+    return false;
+  }
+
+ private:
+  bool first_time_;
+};
+
+// A test that says a binary is interesting first time round, after which
+// interestingness ping pongs between false and true.
+class PingPong : public InterestingnessTest {
+ public:
+  explicit PingPong() : interesting_(false) {}
+
+  bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+    interesting_ = !interesting_;
+    return interesting_;
+  }
+
+ private:
+  bool interesting_;
+};
+
+// A test that says a binary is interesting first time round, thereafter
+// decides at random whether it is interesting.  This allows the logic of the
+// shrinker to be exercised quite a bit.
+class InterestingThenRandom : public InterestingnessTest {
+ public:
+  InterestingThenRandom(const PseudoRandomGenerator& random_generator)
+      : first_time_(true), random_generator_(random_generator) {}
+
+  bool Interesting(const std::vector<uint32_t>&, uint32_t) override {
+    if (first_time_) {
+      first_time_ = false;
+      return true;
+    }
+    return random_generator_.RandomBool();
+  }
+
+ private:
+  bool first_time_;
+  PseudoRandomGenerator random_generator_;
+};
+
+// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to
+// which |transformation_sequence_in| can be applied.  Shrinking of
+// |transformation_sequence_in| gets performed with respect to
+// |interestingness_function|.  If |expected_binary_out| is non-empty, it must
+// match the binary obtained by applying the final shrunk set of
+// transformations, in which case the number of such transformations should
+// equal |expected_transformations_out_size|.
+//
+// The |step_limit| parameter restricts the number of steps that the shrinker
+// will try; it can be set to something small for a faster (but less thorough)
+// test.
+void RunAndCheckShrinker(
+    const spv_target_env& target_env, const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    const protobufs::TransformationSequence& transformation_sequence_in,
+    const Shrinker::InterestingnessFunction& interestingness_function,
+    const std::vector<uint32_t>& expected_binary_out,
+    uint32_t expected_transformations_out_size, uint32_t step_limit) {
+  // Run the shrinker.
+  Shrinker shrinker(target_env, step_limit);
+  shrinker.SetMessageConsumer(kSilentConsumer);
+
+  std::vector<uint32_t> binary_out;
+  protobufs::TransformationSequence transformations_out;
+  Shrinker::ShrinkerResultStatus shrinker_result_status =
+      shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
+                   interestingness_function, &binary_out, &transformations_out);
+  ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
+                  shrinker_result_status ||
+              Shrinker::ShrinkerResultStatus::kStepLimitReached ==
+                  shrinker_result_status);
+
+  // If a non-empty expected binary was provided, check that it matches the
+  // result of shrinking and that the expected number of transformations remain.
+  if (!expected_binary_out.empty()) {
+    ASSERT_EQ(expected_binary_out, binary_out);
+    ASSERT_EQ(expected_transformations_out_size,
+              static_cast<uint32_t>(transformations_out.transformation_size()));
+  }
+}
+
+// Assembles the given |shader| text, and then:
+// - Runs the fuzzer with |seed| to yield a set of transformations
+// - Shrinks the transformation with various interestingness functions,
+//   asserting some properties about the result each time
+void RunFuzzerAndShrinker(const std::string& shader,
+                          const protobufs::FactSequence& initial_facts,
+                          uint32_t seed) {
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+  ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption));
+  ASSERT_TRUE(t.Validate(binary_in));
+
+  // Run the fuzzer and check that it successfully yields a valid binary.
+  std::vector<uint32_t> fuzzer_binary_out;
+  protobufs::TransformationSequence fuzzer_transformation_sequence_out;
+  spvtools::FuzzerOptions fuzzer_options;
+  spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
+  Fuzzer fuzzer(env);
+  fuzzer.SetMessageConsumer(kSilentConsumer);
+  auto fuzzer_result_status =
+      fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out,
+                 &fuzzer_transformation_sequence_out);
+  ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
+  ASSERT_TRUE(t.Validate(fuzzer_binary_out));
+
+  const uint32_t kReasonableStepLimit = 50;
+  const uint32_t kSmallStepLimit = 20;
+
+  // With the AlwaysInteresting test, we should quickly shrink to the original
+  // binary with no transformations remaining.
+  RunAndCheckShrinker(
+      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+      AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit);
+
+  // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
+  RunAndCheckShrinker(
+      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+      OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
+      static_cast<uint32_t>(
+          fuzzer_transformation_sequence_out.transformation_size()),
+      kReasonableStepLimit);
+
+  // The PingPong test is unpredictable; passing an empty expected binary
+  // means that we don't check anything beyond that shrinking completes
+  // successfully.
+  RunAndCheckShrinker(env, binary_in, initial_facts,
+                      fuzzer_transformation_sequence_out,
+                      PingPong().AsFunction(), {}, 0, kSmallStepLimit);
+
+  // The InterestingThenRandom test is unpredictable; passing an empty
+  // expected binary means that we do not check anything about shrinking
+  // results.
+  RunAndCheckShrinker(
+      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
+      InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
+      kSmallStepLimit);
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous1) {
+  // The following SPIR-V came from this GLSL:
+  //
+  // #version 310 es
+  //
+  // void foo() {
+  //   int x;
+  //   x = 2;
+  //   for (int i = 0; i < 100; i++) {
+  //     x += i;
+  //     x = x * 2;
+  //   }
+  //   return;
+  // }
+  //
+  // void main() {
+  //   foo();
+  //   for (int i = 0; i < 10; i++) {
+  //     int j = 20;
+  //     while(j > 0) {
+  //       foo();
+  //       j--;
+  //     }
+  //     do {
+  //       i++;
+  //     } while(i < 4);
+  //   }
+  // }
+
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "foo("
+               OpName %10 "x"
+               OpName %12 "i"
+               OpName %33 "i"
+               OpName %42 "j"
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %33 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %49 RelaxedPrecision
+               OpDecorate %52 RelaxedPrecision
+               OpDecorate %53 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %59 RelaxedPrecision
+               OpDecorate %60 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %64 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 2
+         %13 = OpConstant %8 0
+         %20 = OpConstant %8 100
+         %21 = OpTypeBool
+         %29 = OpConstant %8 1
+         %40 = OpConstant %8 10
+         %43 = OpConstant %8 20
+         %61 = OpConstant %8 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %33 = OpVariable %9 Function
+         %42 = OpVariable %9 Function
+         %32 = OpFunctionCall %2 %6
+               OpStore %33 %13
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %39 = OpLoad %8 %33
+         %41 = OpSLessThan %21 %39 %40
+               OpBranchConditional %41 %35 %36
+         %35 = OpLabel
+               OpStore %42 %43
+               OpBranch %44
+         %44 = OpLabel
+               OpLoopMerge %46 %47 None
+               OpBranch %48
+         %48 = OpLabel
+         %49 = OpLoad %8 %42
+         %50 = OpSGreaterThan %21 %49 %13
+               OpBranchConditional %50 %45 %46
+         %45 = OpLabel
+         %51 = OpFunctionCall %2 %6
+         %52 = OpLoad %8 %42
+         %53 = OpISub %8 %52 %29
+               OpStore %42 %53
+               OpBranch %47
+         %47 = OpLabel
+               OpBranch %44
+         %46 = OpLabel
+               OpBranch %54
+         %54 = OpLabel
+               OpLoopMerge %56 %57 None
+               OpBranch %55
+         %55 = OpLabel
+         %58 = OpLoad %8 %33
+         %59 = OpIAdd %8 %58 %29
+               OpStore %33 %59
+               OpBranch %57
+         %57 = OpLabel
+         %60 = OpLoad %8 %33
+         %62 = OpSLessThan %21 %60 %61
+               OpBranchConditional %62 %54 %56
+         %56 = OpLabel
+               OpBranch %37
+         %37 = OpLabel
+         %63 = OpLoad %8 %33
+         %64 = OpIAdd %8 %63 %29
+               OpStore %33 %64
+               OpBranch %34
+         %36 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+         %12 = OpVariable %9 Function
+               OpStore %10 %11
+               OpStore %12 %13
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpLoad %8 %12
+         %22 = OpSLessThan %21 %19 %20
+               OpBranchConditional %22 %15 %16
+         %15 = OpLabel
+         %23 = OpLoad %8 %12
+         %24 = OpLoad %8 %10
+         %25 = OpIAdd %8 %24 %23
+               OpStore %10 %25
+         %26 = OpLoad %8 %10
+         %27 = OpIMul %8 %26 %11
+               OpStore %10 %27
+               OpBranch %17
+         %17 = OpLabel
+         %28 = OpLoad %8 %12
+         %30 = OpIAdd %8 %28 %29
+               OpStore %12 %30
+               OpBranch %14
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+  )";
+
+  RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2);
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous2) {
+  // The following SPIR-V came from this GLSL, which was then optimized using
+  // spirv-opt with the -O argument:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(location = 0) out vec4 _GLF_color;
+  //
+  // layout(set = 0, binding = 0) uniform buf0 {
+  //  vec2 injectionSwitch;
+  // };
+  // layout(set = 0, binding = 1) uniform buf1 {
+  //  vec2 resolution;
+  // };
+  // bool checkSwap(float a, float b)
+  // {
+  //  return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b;
+  // }
+  // void main()
+  // {
+  //  float data[10];
+  //  for(int i = 0; i < 10; i++)
+  //   {
+  //    data[i] = float(10 - i) * injectionSwitch.y;
+  //   }
+  //  for(int i = 0; i < 9; i++)
+  //   {
+  //    for(int j = 0; j < 10; j++)
+  //     {
+  //      if(j < i + 1)
+  //       {
+  //        continue;
+  //       }
+  //      bool doSwap = checkSwap(data[i], data[j]);
+  //      if(doSwap)
+  //       {
+  //        float temp = data[i];
+  //        data[i] = data[j];
+  //        data[j] = temp;
+  //       }
+  //     }
+  //   }
+  //  if(gl_FragCoord.x < resolution.x / 2.0)
+  //   {
+  //    _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0);
+  //   }
+  //  else
+  //   {
+  //    _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0);
+  //   }
+  // }
+
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %16 %139
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %16 "gl_FragCoord"
+               OpName %23 "buf1"
+               OpMemberName %23 0 "resolution"
+               OpName %25 ""
+               OpName %61 "data"
+               OpName %66 "buf0"
+               OpMemberName %66 0 "injectionSwitch"
+               OpName %68 ""
+               OpName %139 "_GLF_color"
+               OpDecorate %16 BuiltIn FragCoord
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 Block
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+               OpDecorate %64 RelaxedPrecision
+               OpMemberDecorate %66 0 Offset 0
+               OpDecorate %66 Block
+               OpDecorate %68 DescriptorSet 0
+               OpDecorate %68 Binding 0
+               OpDecorate %75 RelaxedPrecision
+               OpDecorate %95 RelaxedPrecision
+               OpDecorate %126 RelaxedPrecision
+               OpDecorate %128 RelaxedPrecision
+               OpDecorate %139 Location 0
+               OpDecorate %182 RelaxedPrecision
+               OpDecorate %183 RelaxedPrecision
+               OpDecorate %184 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeBool
+         %14 = OpTypeVector %6 4
+         %15 = OpTypePointer Input %14
+         %16 = OpVariable %15 Input
+         %17 = OpTypeInt 32 0
+         %18 = OpConstant %17 1
+         %19 = OpTypePointer Input %6
+         %22 = OpTypeVector %6 2
+         %23 = OpTypeStruct %22
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %26 = OpTypeInt 32 1
+         %27 = OpConstant %26 0
+         %28 = OpTypePointer Uniform %6
+         %56 = OpConstant %26 10
+         %58 = OpConstant %17 10
+         %59 = OpTypeArray %6 %58
+         %60 = OpTypePointer Function %59
+         %66 = OpTypeStruct %22
+         %67 = OpTypePointer Uniform %66
+         %68 = OpVariable %67 Uniform
+         %74 = OpConstant %26 1
+         %83 = OpConstant %26 9
+        %129 = OpConstant %17 0
+        %138 = OpTypePointer Output %14
+        %139 = OpVariable %138 Output
+        %144 = OpConstant %26 5
+        %151 = OpConstant %6 1
+        %194 = OpConstant %6 0.5
+        %195 = OpConstant %6 0.100000001
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %61 = OpVariable %60 Function
+               OpBranch %50
+         %50 = OpLabel
+        %182 = OpPhi %26 %27 %5 %75 %51
+         %57 = OpSLessThan %8 %182 %56
+               OpLoopMerge %52 %51 None
+               OpBranchConditional %57 %51 %52
+         %51 = OpLabel
+         %64 = OpISub %26 %56 %182
+         %65 = OpConvertSToF %6 %64
+         %69 = OpAccessChain %28 %68 %27 %18
+         %70 = OpLoad %6 %69
+         %71 = OpFMul %6 %65 %70
+         %72 = OpAccessChain %7 %61 %182
+               OpStore %72 %71
+         %75 = OpIAdd %26 %182 %74
+               OpBranch %50
+         %52 = OpLabel
+               OpBranch %77
+         %77 = OpLabel
+        %183 = OpPhi %26 %27 %52 %128 %88
+         %84 = OpSLessThan %8 %183 %83
+               OpLoopMerge %79 %88 None
+               OpBranchConditional %84 %78 %79
+         %78 = OpLabel
+               OpBranch %86
+         %86 = OpLabel
+        %184 = OpPhi %26 %27 %78 %126 %89
+         %92 = OpSLessThan %8 %184 %56
+               OpLoopMerge %88 %89 None
+               OpBranchConditional %92 %87 %88
+         %87 = OpLabel
+         %95 = OpIAdd %26 %183 %74
+         %96 = OpSLessThan %8 %184 %95
+               OpSelectionMerge %98 None
+               OpBranchConditional %96 %97 %98
+         %97 = OpLabel
+               OpBranch %89
+         %98 = OpLabel
+        %104 = OpAccessChain %7 %61 %183
+        %105 = OpLoad %6 %104
+        %107 = OpAccessChain %7 %61 %184
+        %108 = OpLoad %6 %107
+        %166 = OpAccessChain %19 %16 %18
+        %167 = OpLoad %6 %166
+        %168 = OpAccessChain %28 %25 %27 %18
+        %169 = OpLoad %6 %168
+        %170 = OpFMul %6 %169 %194
+        %171 = OpFOrdLessThan %8 %167 %170
+               OpSelectionMerge %172 None
+               OpBranchConditional %171 %173 %174
+        %173 = OpLabel
+        %177 = OpFOrdGreaterThan %8 %105 %108
+               OpBranch %172
+        %174 = OpLabel
+        %180 = OpFOrdLessThan %8 %105 %108
+               OpBranch %172
+        %172 = OpLabel
+        %186 = OpPhi %8 %177 %173 %180 %174
+               OpSelectionMerge %112 None
+               OpBranchConditional %186 %111 %112
+        %111 = OpLabel
+        %116 = OpLoad %6 %104
+        %120 = OpLoad %6 %107
+               OpStore %104 %120
+               OpStore %107 %116
+               OpBranch %112
+        %112 = OpLabel
+               OpBranch %89
+         %89 = OpLabel
+        %126 = OpIAdd %26 %184 %74
+               OpBranch %86
+         %88 = OpLabel
+        %128 = OpIAdd %26 %183 %74
+               OpBranch %77
+         %79 = OpLabel
+        %130 = OpAccessChain %19 %16 %129
+        %131 = OpLoad %6 %130
+        %132 = OpAccessChain %28 %25 %27 %129
+        %133 = OpLoad %6 %132
+        %134 = OpFMul %6 %133 %194
+        %135 = OpFOrdLessThan %8 %131 %134
+               OpSelectionMerge %137 None
+               OpBranchConditional %135 %136 %153
+        %136 = OpLabel
+        %140 = OpAccessChain %7 %61 %27
+        %141 = OpLoad %6 %140
+        %143 = OpFMul %6 %141 %195
+        %145 = OpAccessChain %7 %61 %144
+        %146 = OpLoad %6 %145
+        %147 = OpFMul %6 %146 %195
+        %148 = OpAccessChain %7 %61 %83
+        %149 = OpLoad %6 %148
+        %150 = OpFMul %6 %149 %195
+        %152 = OpCompositeConstruct %14 %143 %147 %150 %151
+               OpStore %139 %152
+               OpBranch %137
+        %153 = OpLabel
+        %154 = OpAccessChain %7 %61 %144
+        %155 = OpLoad %6 %154
+        %156 = OpFMul %6 %155 %195
+        %157 = OpAccessChain %7 %61 %83
+        %158 = OpLoad %6 %157
+        %159 = OpFMul %6 %158 %195
+        %160 = OpAccessChain %7 %61 %27
+        %161 = OpLoad %6 %160
+        %162 = OpFMul %6 %161 %195
+        %163 = OpCompositeConstruct %14 %156 %159 %162 %151
+               OpStore %139 %163
+               OpBranch %137
+        %137 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19);
+}
+
+TEST(FuzzerShrinkerTest, Miscellaneous3) {
+  // The following SPIR-V came from this GLSL, which was then optimized using
+  // spirv-opt with the -O argument:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(location = 0) out vec4 _GLF_color;
+  //
+  // layout(set = 0, binding = 0) uniform buf0 {
+  //  vec2 resolution;
+  // };
+  // void main(void)
+  // {
+  //  float A[50];
+  //  for(
+  //      int i = 0;
+  //      i < 200;
+  //      i ++
+  //  )
+  //   {
+  //    if(i >= int(resolution.x))
+  //     {
+  //      break;
+  //     }
+  //    if((4 * (i / 4)) == i)
+  //     {
+  //      A[i / 4] = float(i);
+  //     }
+  //   }
+  //  for(
+  //      int i = 0;
+  //      i < 50;
+  //      i ++
+  //  )
+  //   {
+  //    if(i < int(gl_FragCoord.x))
+  //     {
+  //      break;
+  //     }
+  //    if(i > 0)
+  //     {
+  //      A[i] += A[i - 1];
+  //     }
+  //   }
+  //  if(int(gl_FragCoord.x) < 20)
+  //   {
+  //    _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
+  //   }
+  //  else
+  //   if(int(gl_FragCoord.x) < 40)
+  //    {
+  //     _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
+  //    }
+  //   else
+  //    if(int(gl_FragCoord.x) < 60)
+  //     {
+  //      _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
+  //      1.0, 1.0);
+  //     }
+  //    else
+  //     if(int(gl_FragCoord.x) < 80)
+  //      {
+  //       _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
+  //       1.0, 1.0);
+  //      }
+  //     else
+  //      if(int(gl_FragCoord.x) < 100)
+  //       {
+  //        _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
+  //        1.0, 1.0);
+  //       }
+  //      else
+  //       if(int(gl_FragCoord.x) < 120)
+  //        {
+  //         _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
+  //         1.0, 1.0);
+  //        }
+  //       else
+  //        if(int(gl_FragCoord.x) < 140)
+  //         {
+  //          _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
+  //          1.0, 1.0);
+  //         }
+  //        else
+  //         if(int(gl_FragCoord.x) < 160)
+  //          {
+  //           _GLF_color = vec4(A[35] / resolution.x, A[39] /
+  //           resolution.y, 1.0, 1.0);
+  //          }
+  //         else
+  //          if(int(gl_FragCoord.x) < 180)
+  //           {
+  //            _GLF_color = vec4(A[40] / resolution.x, A[44] /
+  //            resolution.y, 1.0, 1.0);
+  //           }
+  //          else
+  //           if(int(gl_FragCoord.x) < 180)
+  //            {
+  //             _GLF_color = vec4(A[45] / resolution.x, A[49] /
+  //             resolution.y, 1.0, 1.0);
+  //            }
+  //           else
+  //            {
+  //             discard;
+  //            }
+  // }
+
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %68 %100
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %22 "buf0"
+               OpMemberName %22 0 "resolution"
+               OpName %24 ""
+               OpName %46 "A"
+               OpName %68 "gl_FragCoord"
+               OpName %100 "_GLF_color"
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %68 BuiltIn FragCoord
+               OpDecorate %83 RelaxedPrecision
+               OpDecorate %91 RelaxedPrecision
+               OpDecorate %100 Location 0
+               OpDecorate %302 RelaxedPrecision
+               OpDecorate %304 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 200
+         %17 = OpTypeBool
+         %20 = OpTypeFloat 32
+         %21 = OpTypeVector %20 2
+         %22 = OpTypeStruct %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypeInt 32 0
+         %26 = OpConstant %25 0
+         %27 = OpTypePointer Uniform %20
+         %35 = OpConstant %6 4
+         %43 = OpConstant %25 50
+         %44 = OpTypeArray %20 %43
+         %45 = OpTypePointer Function %44
+         %51 = OpTypePointer Function %20
+         %54 = OpConstant %6 1
+         %63 = OpConstant %6 50
+         %66 = OpTypeVector %20 4
+         %67 = OpTypePointer Input %66
+         %68 = OpVariable %67 Input
+         %69 = OpTypePointer Input %20
+         %95 = OpConstant %6 20
+         %99 = OpTypePointer Output %66
+        %100 = OpVariable %99 Output
+        %108 = OpConstant %25 1
+        %112 = OpConstant %20 1
+        %118 = OpConstant %6 40
+        %122 = OpConstant %6 5
+        %128 = OpConstant %6 9
+        %139 = OpConstant %6 60
+        %143 = OpConstant %6 10
+        %149 = OpConstant %6 14
+        %160 = OpConstant %6 80
+        %164 = OpConstant %6 15
+        %170 = OpConstant %6 19
+        %181 = OpConstant %6 100
+        %190 = OpConstant %6 24
+        %201 = OpConstant %6 120
+        %205 = OpConstant %6 25
+        %211 = OpConstant %6 29
+        %222 = OpConstant %6 140
+        %226 = OpConstant %6 30
+        %232 = OpConstant %6 34
+        %243 = OpConstant %6 160
+        %247 = OpConstant %6 35
+        %253 = OpConstant %6 39
+        %264 = OpConstant %6 180
+        %273 = OpConstant %6 44
+        %287 = OpConstant %6 45
+        %293 = OpConstant %6 49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %46 = OpVariable %45 Function
+               OpBranch %10
+         %10 = OpLabel
+        %302 = OpPhi %6 %9 %5 %55 %42
+         %18 = OpSLessThan %17 %302 %16
+               OpLoopMerge %12 %42 None
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %28 = OpAccessChain %27 %24 %9 %26
+         %29 = OpLoad %20 %28
+         %30 = OpConvertFToS %6 %29
+         %31 = OpSGreaterThanEqual %17 %302 %30
+               OpSelectionMerge %33 None
+               OpBranchConditional %31 %32 %33
+         %32 = OpLabel
+               OpBranch %12
+         %33 = OpLabel
+         %37 = OpSDiv %6 %302 %35
+         %38 = OpIMul %6 %35 %37
+         %40 = OpIEqual %17 %38 %302
+               OpSelectionMerge %42 None
+               OpBranchConditional %40 %41 %42
+         %41 = OpLabel
+         %50 = OpConvertSToF %20 %302
+         %52 = OpAccessChain %51 %46 %37
+               OpStore %52 %50
+               OpBranch %42
+         %42 = OpLabel
+         %55 = OpIAdd %6 %302 %54
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %57
+         %57 = OpLabel
+        %304 = OpPhi %6 %9 %12 %91 %80
+         %64 = OpSLessThan %17 %304 %63
+               OpLoopMerge %59 %80 None
+               OpBranchConditional %64 %58 %59
+         %58 = OpLabel
+         %70 = OpAccessChain %69 %68 %26
+         %71 = OpLoad %20 %70
+         %72 = OpConvertFToS %6 %71
+         %73 = OpSLessThan %17 %304 %72
+               OpSelectionMerge %75 None
+               OpBranchConditional %73 %74 %75
+         %74 = OpLabel
+               OpBranch %59
+         %75 = OpLabel
+         %78 = OpSGreaterThan %17 %304 %9
+               OpSelectionMerge %80 None
+               OpBranchConditional %78 %79 %80
+         %79 = OpLabel
+         %83 = OpISub %6 %304 %54
+         %84 = OpAccessChain %51 %46 %83
+         %85 = OpLoad %20 %84
+         %86 = OpAccessChain %51 %46 %304
+         %87 = OpLoad %20 %86
+         %88 = OpFAdd %20 %87 %85
+               OpStore %86 %88
+               OpBranch %80
+         %80 = OpLabel
+         %91 = OpIAdd %6 %304 %54
+               OpBranch %57
+         %59 = OpLabel
+         %92 = OpAccessChain %69 %68 %26
+         %93 = OpLoad %20 %92
+         %94 = OpConvertFToS %6 %93
+         %96 = OpSLessThan %17 %94 %95
+               OpSelectionMerge %98 None
+               OpBranchConditional %96 %97 %114
+         %97 = OpLabel
+        %101 = OpAccessChain %51 %46 %9
+        %102 = OpLoad %20 %101
+        %103 = OpAccessChain %27 %24 %9 %26
+        %104 = OpLoad %20 %103
+        %105 = OpFDiv %20 %102 %104
+        %106 = OpAccessChain %51 %46 %35
+        %107 = OpLoad %20 %106
+        %109 = OpAccessChain %27 %24 %9 %108
+        %110 = OpLoad %20 %109
+        %111 = OpFDiv %20 %107 %110
+        %113 = OpCompositeConstruct %66 %105 %111 %112 %112
+               OpStore %100 %113
+               OpBranch %98
+        %114 = OpLabel
+        %119 = OpSLessThan %17 %94 %118
+               OpSelectionMerge %121 None
+               OpBranchConditional %119 %120 %135
+        %120 = OpLabel
+        %123 = OpAccessChain %51 %46 %122
+        %124 = OpLoad %20 %123
+        %125 = OpAccessChain %27 %24 %9 %26
+        %126 = OpLoad %20 %125
+        %127 = OpFDiv %20 %124 %126
+        %129 = OpAccessChain %51 %46 %128
+        %130 = OpLoad %20 %129
+        %131 = OpAccessChain %27 %24 %9 %108
+        %132 = OpLoad %20 %131
+        %133 = OpFDiv %20 %130 %132
+        %134 = OpCompositeConstruct %66 %127 %133 %112 %112
+               OpStore %100 %134
+               OpBranch %121
+        %135 = OpLabel
+        %140 = OpSLessThan %17 %94 %139
+               OpSelectionMerge %142 None
+               OpBranchConditional %140 %141 %156
+        %141 = OpLabel
+        %144 = OpAccessChain %51 %46 %143
+        %145 = OpLoad %20 %144
+        %146 = OpAccessChain %27 %24 %9 %26
+        %147 = OpLoad %20 %146
+        %148 = OpFDiv %20 %145 %147
+        %150 = OpAccessChain %51 %46 %149
+        %151 = OpLoad %20 %150
+        %152 = OpAccessChain %27 %24 %9 %108
+        %153 = OpLoad %20 %152
+        %154 = OpFDiv %20 %151 %153
+        %155 = OpCompositeConstruct %66 %148 %154 %112 %112
+               OpStore %100 %155
+               OpBranch %142
+        %156 = OpLabel
+        %161 = OpSLessThan %17 %94 %160
+               OpSelectionMerge %163 None
+               OpBranchConditional %161 %162 %177
+        %162 = OpLabel
+        %165 = OpAccessChain %51 %46 %164
+        %166 = OpLoad %20 %165
+        %167 = OpAccessChain %27 %24 %9 %26
+        %168 = OpLoad %20 %167
+        %169 = OpFDiv %20 %166 %168
+        %171 = OpAccessChain %51 %46 %170
+        %172 = OpLoad %20 %171
+        %173 = OpAccessChain %27 %24 %9 %108
+        %174 = OpLoad %20 %173
+        %175 = OpFDiv %20 %172 %174
+        %176 = OpCompositeConstruct %66 %169 %175 %112 %112
+               OpStore %100 %176
+               OpBranch %163
+        %177 = OpLabel
+        %182 = OpSLessThan %17 %94 %181
+               OpSelectionMerge %184 None
+               OpBranchConditional %182 %183 %197
+        %183 = OpLabel
+        %185 = OpAccessChain %51 %46 %95
+        %186 = OpLoad %20 %185
+        %187 = OpAccessChain %27 %24 %9 %26
+        %188 = OpLoad %20 %187
+        %189 = OpFDiv %20 %186 %188
+        %191 = OpAccessChain %51 %46 %190
+        %192 = OpLoad %20 %191
+        %193 = OpAccessChain %27 %24 %9 %108
+        %194 = OpLoad %20 %193
+        %195 = OpFDiv %20 %192 %194
+        %196 = OpCompositeConstruct %66 %189 %195 %112 %112
+               OpStore %100 %196
+               OpBranch %184
+        %197 = OpLabel
+        %202 = OpSLessThan %17 %94 %201
+               OpSelectionMerge %204 None
+               OpBranchConditional %202 %203 %218
+        %203 = OpLabel
+        %206 = OpAccessChain %51 %46 %205
+        %207 = OpLoad %20 %206
+        %208 = OpAccessChain %27 %24 %9 %26
+        %209 = OpLoad %20 %208
+        %210 = OpFDiv %20 %207 %209
+        %212 = OpAccessChain %51 %46 %211
+        %213 = OpLoad %20 %212
+        %214 = OpAccessChain %27 %24 %9 %108
+        %215 = OpLoad %20 %214
+        %216 = OpFDiv %20 %213 %215
+        %217 = OpCompositeConstruct %66 %210 %216 %112 %112
+               OpStore %100 %217
+               OpBranch %204
+        %218 = OpLabel
+        %223 = OpSLessThan %17 %94 %222
+               OpSelectionMerge %225 None
+               OpBranchConditional %223 %224 %239
+        %224 = OpLabel
+        %227 = OpAccessChain %51 %46 %226
+        %228 = OpLoad %20 %227
+        %229 = OpAccessChain %27 %24 %9 %26
+        %230 = OpLoad %20 %229
+        %231 = OpFDiv %20 %228 %230
+        %233 = OpAccessChain %51 %46 %232
+        %234 = OpLoad %20 %233
+        %235 = OpAccessChain %27 %24 %9 %108
+        %236 = OpLoad %20 %235
+        %237 = OpFDiv %20 %234 %236
+        %238 = OpCompositeConstruct %66 %231 %237 %112 %112
+               OpStore %100 %238
+               OpBranch %225
+        %239 = OpLabel
+        %244 = OpSLessThan %17 %94 %243
+               OpSelectionMerge %246 None
+               OpBranchConditional %244 %245 %260
+        %245 = OpLabel
+        %248 = OpAccessChain %51 %46 %247
+        %249 = OpLoad %20 %248
+        %250 = OpAccessChain %27 %24 %9 %26
+        %251 = OpLoad %20 %250
+        %252 = OpFDiv %20 %249 %251
+        %254 = OpAccessChain %51 %46 %253
+        %255 = OpLoad %20 %254
+        %256 = OpAccessChain %27 %24 %9 %108
+        %257 = OpLoad %20 %256
+        %258 = OpFDiv %20 %255 %257
+        %259 = OpCompositeConstruct %66 %252 %258 %112 %112
+               OpStore %100 %259
+               OpBranch %246
+        %260 = OpLabel
+        %265 = OpSLessThan %17 %94 %264
+               OpSelectionMerge %267 None
+               OpBranchConditional %265 %266 %280
+        %266 = OpLabel
+        %268 = OpAccessChain %51 %46 %118
+        %269 = OpLoad %20 %268
+        %270 = OpAccessChain %27 %24 %9 %26
+        %271 = OpLoad %20 %270
+        %272 = OpFDiv %20 %269 %271
+        %274 = OpAccessChain %51 %46 %273
+        %275 = OpLoad %20 %274
+        %276 = OpAccessChain %27 %24 %9 %108
+        %277 = OpLoad %20 %276
+        %278 = OpFDiv %20 %275 %277
+        %279 = OpCompositeConstruct %66 %272 %278 %112 %112
+               OpStore %100 %279
+               OpBranch %267
+        %280 = OpLabel
+               OpSelectionMerge %285 None
+               OpBranchConditional %265 %285 %300
+        %285 = OpLabel
+        %288 = OpAccessChain %51 %46 %287
+        %289 = OpLoad %20 %288
+        %290 = OpAccessChain %27 %24 %9 %26
+        %291 = OpLoad %20 %290
+        %292 = OpFDiv %20 %289 %291
+        %294 = OpAccessChain %51 %46 %293
+        %295 = OpLoad %20 %294
+        %296 = OpAccessChain %27 %24 %9 %108
+        %297 = OpLoad %20 %296
+        %298 = OpFDiv %20 %295 %297
+        %299 = OpCompositeConstruct %66 %292 %298 %112 %112
+               OpStore %100 %299
+               OpBranch %267
+        %300 = OpLabel
+               OpKill
+        %267 = OpLabel
+               OpBranch %246
+        %246 = OpLabel
+               OpBranch %225
+        %225 = OpLabel
+               OpBranch %204
+        %204 = OpLabel
+               OpBranch %184
+        %184 = OpLabel
+               OpBranch %163
+        %163 = OpLabel
+               OpBranch %142
+        %142 = OpLabel
+               OpBranch %121
+        %121 = OpLabel
+               OpBranch %98
+         %98 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Add the facts "resolution.x == 250" and "resolution.y == 100".
+  protobufs::FactSequence facts;
+  {
+    protobufs::FactConstantUniform resolution_x_eq_250;
+    *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(0, 0, {0, 0});
+    *resolution_x_eq_250.mutable_constant_word()->Add() = 250;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_x_eq_250;
+    *facts.mutable_fact()->Add() = temp;
+  }
+  {
+    protobufs::FactConstantUniform resolution_y_eq_100;
+    *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(0, 0, {0, 1});
+    *resolution_y_eq_100.mutable_constant_word()->Add() = 100;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_y_eq_100;
+    *facts.mutable_fact()->Add() = temp;
+  }
+
+  // Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndShrinker(shader, facts, 194);
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_constant_boolean_test.cpp b/test/fuzz/transformation_add_constant_boolean_test.cpp
new file mode 100644
index 0000000..f51c46b
--- /dev/null
+++ b/test/fuzz/transformation_add_constant_boolean_test.cpp
@@ -0,0 +1,141 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_constant_boolean.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddConstantBooleanTest, NeitherPresentInitiallyAddBoth) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %6 = OpTypeBool
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // True and false can both be added as neither is present.
+  ASSERT_TRUE(TransformationAddConstantBoolean(7, true).IsApplicable(
+      context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddConstantBoolean(7, false).IsApplicable(
+      context.get(), fact_manager));
+
+  // Id 5 is already taken.
+  ASSERT_FALSE(TransformationAddConstantBoolean(5, true).IsApplicable(
+      context.get(), fact_manager));
+
+  auto add_true = TransformationAddConstantBoolean(7, true);
+  auto add_false = TransformationAddConstantBoolean(8, false);
+
+  ASSERT_TRUE(add_true.IsApplicable(context.get(), fact_manager));
+  add_true.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Having added true, we cannot add it again with the same id.
+  ASSERT_FALSE(add_true.IsApplicable(context.get(), fact_manager));
+  // But we can add it with a different id.
+  auto add_true_again = TransformationAddConstantBoolean(100, true);
+  ASSERT_TRUE(add_true_again.IsApplicable(context.get(), fact_manager));
+  add_true_again.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_false.IsApplicable(context.get(), fact_manager));
+  add_false.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Having added false, we cannot add it again with the same id.
+  ASSERT_FALSE(add_false.IsApplicable(context.get(), fact_manager));
+  // But we can add it with a different id.
+  auto add_false_again = TransformationAddConstantBoolean(101, false);
+  ASSERT_TRUE(add_false_again.IsApplicable(context.get(), fact_manager));
+  add_false_again.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %6 = OpTypeBool
+          %3 = OpTypeFunction %2
+          %7 = OpConstantTrue %6
+        %100 = OpConstantTrue %6
+          %8 = OpConstantFalse %6
+        %101 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddConstantBooleanTest, NoOpTypeBoolPresent) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Neither true nor false can be added as OpTypeBool is not present.
+  ASSERT_FALSE(TransformationAddConstantBoolean(6, true).IsApplicable(
+      context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddConstantBoolean(6, false).IsApplicable(
+      context.get(), fact_manager));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_constant_scalar_test.cpp b/test/fuzz/transformation_add_constant_scalar_test.cpp
new file mode 100644
index 0000000..b156111
--- /dev/null
+++ b/test/fuzz/transformation_add_constant_scalar_test.cpp
@@ -0,0 +1,187 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_constant_scalar.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddConstantScalarTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "y"
+               OpName %16 "z"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 2
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %15 Function
+               OpStore %8 %9
+               OpStore %12 %13
+               OpStore %16 %17
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  const float float_values[2] = {3.0, 30.0};
+  uint32_t uint_for_float[2];
+  memcpy(uint_for_float, float_values, sizeof(float_values));
+
+  auto add_signed_int_1 = TransformationAddConstantScalar(100, 6, {1});
+  auto add_signed_int_10 = TransformationAddConstantScalar(101, 6, {10});
+  auto add_unsigned_int_2 = TransformationAddConstantScalar(102, 10, {2});
+  auto add_unsigned_int_20 = TransformationAddConstantScalar(103, 10, {20});
+  auto add_float_3 =
+      TransformationAddConstantScalar(104, 14, {uint_for_float[0]});
+  auto add_float_30 =
+      TransformationAddConstantScalar(105, 14, {uint_for_float[1]});
+  auto bad_add_float_30_id_already_used =
+      TransformationAddConstantScalar(104, 14, {uint_for_float[1]});
+  auto bad_id_already_used = TransformationAddConstantScalar(1, 6, {1});
+  auto bad_no_data = TransformationAddConstantScalar(100, 6, {});
+  auto bad_too_much_data = TransformationAddConstantScalar(100, 6, {1, 2});
+  auto bad_type_id_does_not_exist =
+      TransformationAddConstantScalar(108, 2020, {uint_for_float[0]});
+  auto bad_type_id_is_not_a_type = TransformationAddConstantScalar(109, 9, {0});
+  auto bad_type_id_is_void = TransformationAddConstantScalar(110, 2, {0});
+  auto bad_type_id_is_pointer = TransformationAddConstantScalar(111, 11, {0});
+
+  // Id is already in use.
+  ASSERT_FALSE(bad_id_already_used.IsApplicable(context.get(), fact_manager));
+
+  // At least one word of data must be provided.
+  ASSERT_FALSE(bad_no_data.IsApplicable(context.get(), fact_manager));
+
+  // Cannot give two data words for a 32-bit type.
+  ASSERT_FALSE(bad_too_much_data.IsApplicable(context.get(), fact_manager));
+
+  // Type id does not exist
+  ASSERT_FALSE(
+      bad_type_id_does_not_exist.IsApplicable(context.get(), fact_manager));
+
+  // Type id is not a type
+  ASSERT_FALSE(
+      bad_type_id_is_not_a_type.IsApplicable(context.get(), fact_manager));
+
+  // Type id is void
+  ASSERT_FALSE(bad_type_id_is_void.IsApplicable(context.get(), fact_manager));
+
+  // Type id is pointer
+  ASSERT_FALSE(
+      bad_type_id_is_pointer.IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(add_signed_int_1.IsApplicable(context.get(), fact_manager));
+  add_signed_int_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_signed_int_10.IsApplicable(context.get(), fact_manager));
+  add_signed_int_10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_unsigned_int_2.IsApplicable(context.get(), fact_manager));
+  add_unsigned_int_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_unsigned_int_20.IsApplicable(context.get(), fact_manager));
+  add_unsigned_int_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_float_3.IsApplicable(context.get(), fact_manager));
+  add_float_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(add_float_30.IsApplicable(context.get(), fact_manager));
+  add_float_30.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_FALSE(bad_add_float_30_id_already_used.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "y"
+               OpName %16 "z"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 2
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 3
+        %100 = OpConstant %6 1
+        %101 = OpConstant %6 10
+        %102 = OpConstant %10 2
+        %103 = OpConstant %10 20
+        %104 = OpConstant %14 3
+        %105 = OpConstant %14 30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %15 Function
+               OpStore %8 %9
+               OpStore %12 %13
+               OpStore %16 %17
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_dead_break_test.cpp b/test/fuzz/transformation_add_dead_break_test.cpp
new file mode 100644
index 0000000..99bc9d9
--- /dev/null
+++ b/test/fuzz/transformation_add_dead_break_test.cpp
@@ -0,0 +1,2085 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_dead_break.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddDeadBreakTest, BreaksOutOfSimpleIf) {
+  // For a simple if-then-else, checks that some dead break scenarios are
+  // possible, and sanity-checks that some illegal scenarios are indeed not
+  // allowed.
+
+  // The SPIR-V for this test is adapted from the following GLSL, by separating
+  // some assignments into their own basic blocks, and adding constants for true
+  // and false:
+  //
+  // void main() {
+  //   int x;
+  //   int y;
+  //   x = 1;
+  //   if (x < y) {
+  //     x = 2;
+  //     x = 3;
+  //   } else {
+  //     y = 2;
+  //     y = 3;
+  //   }
+  //   x = y;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 3
+         %25 = OpConstantTrue %13
+         %26 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %19
+         %15 = OpLabel
+               OpStore %8 %17
+               OpBranch %21
+         %21 = OpLabel
+               OpStore %8 %18
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %16
+         %19 = OpLabel
+               OpStore %11 %17
+               OpBranch %23
+         %23 = OpLabel
+               OpStore %11 %18
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %20 = OpLoad %6 %11
+               OpStore %8 %20
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+
+  const uint32_t merge_block = 16;
+
+  // These are all possibilities.
+  ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(15, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(21, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(22, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(19, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(23, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(24, merge_block, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 100 is not a block id.
+  ASSERT_FALSE(TransformationAddDeadBreak(100, merge_block, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(15, 100, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 24 is not a merge block.
+  ASSERT_FALSE(TransformationAddDeadBreak(15, 24, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // These are the transformations we will apply.
+  auto transformation1 = TransformationAddDeadBreak(15, merge_block, true, {});
+  auto transformation2 = TransformationAddDeadBreak(21, merge_block, false, {});
+  auto transformation3 = TransformationAddDeadBreak(22, merge_block, true, {});
+  auto transformation4 = TransformationAddDeadBreak(19, merge_block, false, {});
+  auto transformation5 = TransformationAddDeadBreak(23, merge_block, true, {});
+  auto transformation6 = TransformationAddDeadBreak(24, merge_block, false, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 3
+         %25 = OpConstantTrue %13
+         %26 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %19
+         %15 = OpLabel
+               OpStore %8 %17
+               OpBranchConditional %25 %21 %16
+         %21 = OpLabel
+               OpStore %8 %18
+               OpBranchConditional %26 %16 %22
+         %22 = OpLabel
+               OpBranchConditional %25 %16 %16
+         %19 = OpLabel
+               OpStore %11 %17
+               OpBranchConditional %26 %16 %23
+         %23 = OpLabel
+               OpStore %11 %18
+               OpBranchConditional %25 %24 %16
+         %24 = OpLabel
+               OpBranchConditional %26 %16 %16
+         %16 = OpLabel
+         %20 = OpLoad %6 %11
+               OpStore %8 %20
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, BreakOutOfNestedIfs) {
+  // Checks some allowed and disallowed scenarios for nests of ifs.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   int x;
+  //   int y;
+  //   x = 1;
+  //   if (x < y) {
+  //     x = 2;
+  //     x = 3;
+  //     if (x == y) {
+  //       y = 3;
+  //     }
+  //   } else {
+  //     y = 2;
+  //     y = 3;
+  //   }
+  //   if (x == y) {
+  //     x = 2;
+  //   }
+  //   x = y;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 3
+         %31 = OpConstantTrue %13
+         %32 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %24
+         %15 = OpLabel
+               OpStore %8 %17
+               OpBranch %33
+         %33 = OpLabel
+               OpStore %8 %18
+         %19 = OpLoad %6 %8
+               OpBranch %34
+         %34 = OpLabel
+         %20 = OpLoad %6 %11
+         %21 = OpIEqual %13 %19 %20
+               OpSelectionMerge %23 None
+               OpBranchConditional %21 %22 %23
+         %22 = OpLabel
+               OpStore %11 %18
+               OpBranch %35
+         %35 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %16
+         %24 = OpLabel
+               OpStore %11 %17
+               OpBranch %36
+         %36 = OpLabel
+               OpStore %11 %18
+               OpBranch %16
+         %16 = OpLabel
+         %25 = OpLoad %6 %8
+               OpBranch %37
+         %37 = OpLabel
+         %26 = OpLoad %6 %11
+         %27 = OpIEqual %13 %25 %26
+               OpSelectionMerge %29 None
+               OpBranchConditional %27 %28 %29
+         %28 = OpLabel
+               OpStore %8 %17
+               OpBranch %38
+         %38 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+         %30 = OpLoad %6 %11
+               OpStore %8 %30
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // The header and merge blocks
+  const uint32_t header_inner = 34;
+  const uint32_t merge_inner = 23;
+  const uint32_t header_outer = 5;
+  const uint32_t merge_outer = 16;
+  const uint32_t header_after = 37;
+  const uint32_t merge_after = 29;
+
+  // The non-merge-nor-header blocks in each construct
+  const uint32_t inner_block_1 = 22;
+  const uint32_t inner_block_2 = 35;
+  const uint32_t outer_block_1 = 15;
+  const uint32_t outer_block_2 = 33;
+  const uint32_t outer_block_3 = 24;
+  const uint32_t outer_block_4 = 36;
+  const uint32_t after_block_1 = 28;
+  const uint32_t after_block_2 = 38;
+
+  // Fine to break from a construct to its merge
+  ASSERT_TRUE(TransformationAddDeadBreak(inner_block_1, merge_inner, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(inner_block_2, merge_inner, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(outer_block_1, merge_outer, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(outer_block_2, merge_outer, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(outer_block_3, merge_outer, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(outer_block_4, merge_outer, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(after_block_1, merge_after, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(after_block_2, merge_after, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break to the wrong merge (whether enclosing or not)
+  ASSERT_FALSE(TransformationAddDeadBreak(inner_block_1, merge_outer, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(inner_block_2, merge_after, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, merge_inner, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(outer_block_2, merge_after, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(after_block_1, merge_inner, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(after_block_2, merge_outer, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break from header (as it does not branch unconditionally)
+  ASSERT_FALSE(TransformationAddDeadBreak(header_inner, merge_inner, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_outer, merge_outer, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_after, merge_after, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break to non-merge
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(inner_block_1, inner_block_2, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(outer_block_2, after_block_1, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(outer_block_1, header_after, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto transformation1 =
+      TransformationAddDeadBreak(inner_block_1, merge_inner, true, {});
+  auto transformation2 =
+      TransformationAddDeadBreak(inner_block_2, merge_inner, false, {});
+  auto transformation3 =
+      TransformationAddDeadBreak(outer_block_1, merge_outer, true, {});
+  auto transformation4 =
+      TransformationAddDeadBreak(outer_block_2, merge_outer, false, {});
+  auto transformation5 =
+      TransformationAddDeadBreak(outer_block_3, merge_outer, true, {});
+  auto transformation6 =
+      TransformationAddDeadBreak(outer_block_4, merge_outer, false, {});
+  auto transformation7 =
+      TransformationAddDeadBreak(after_block_1, merge_after, true, {});
+  auto transformation8 =
+      TransformationAddDeadBreak(after_block_2, merge_after, false, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
+  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation8.IsApplicable(context.get(), fact_manager));
+  transformation8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 3
+         %31 = OpConstantTrue %13
+         %32 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %24
+         %15 = OpLabel
+               OpStore %8 %17
+               OpBranchConditional %31 %33 %16
+         %33 = OpLabel
+               OpStore %8 %18
+         %19 = OpLoad %6 %8
+               OpBranchConditional %32 %16 %34
+         %34 = OpLabel
+         %20 = OpLoad %6 %11
+         %21 = OpIEqual %13 %19 %20
+               OpSelectionMerge %23 None
+               OpBranchConditional %21 %22 %23
+         %22 = OpLabel
+               OpStore %11 %18
+               OpBranchConditional %31 %35 %23
+         %35 = OpLabel
+               OpBranchConditional %32 %23 %23
+         %23 = OpLabel
+               OpBranch %16
+         %24 = OpLabel
+               OpStore %11 %17
+               OpBranchConditional %31 %36 %16
+         %36 = OpLabel
+               OpStore %11 %18
+               OpBranchConditional %32 %16 %16
+         %16 = OpLabel
+         %25 = OpLoad %6 %8
+               OpBranch %37
+         %37 = OpLabel
+         %26 = OpLoad %6 %11
+         %27 = OpIEqual %13 %25 %26
+               OpSelectionMerge %29 None
+               OpBranchConditional %27 %28 %29
+         %28 = OpLabel
+               OpStore %8 %17
+               OpBranchConditional %31 %38 %29
+         %38 = OpLabel
+               OpBranchConditional %32 %29 %29
+         %29 = OpLabel
+         %30 = OpLoad %6 %11
+               OpStore %8 %30
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, BreakOutOfNestedSwitches) {
+  // Checks some allowed and disallowed scenarios for nests of switches.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   int x;
+  //   int y;
+  //   x = 1;
+  //   if (x < y) {
+  //     switch (x) {
+  //       case 0:
+  //       case 1:
+  //         if (x == y) {
+  //         }
+  //         x = 2;
+  //         break;
+  //       case 3:
+  //         if (y == 4) {
+  //           y = 2;
+  //           x = 3;
+  //         }
+  //       case 10:
+  //         break;
+  //       default:
+  //         switch (y) {
+  //           case 1:
+  //             break;
+  //           case 2:
+  //             x = 4;
+  //             y = 2;
+  //           default:
+  //             x = 3;
+  //             break;
+  //         }
+  //     }
+  //   } else {
+  //     switch (y) {
+  //       case 1:
+  //         x = 4;
+  //       case 2:
+  //         y = 3;
+  //       default:
+  //         x = y;
+  //         break;
+  //     }
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %29 = OpConstant %6 2
+         %32 = OpConstant %6 4
+         %36 = OpConstant %6 3
+         %60 = OpConstantTrue %13
+         %61 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %47
+         %15 = OpLabel
+         %17 = OpLoad %6 %8
+               OpSelectionMerge %22 None
+               OpSwitch %17 %21 0 %18 1 %18 3 %19 10 %20
+         %21 = OpLabel
+         %38 = OpLoad %6 %11
+               OpSelectionMerge %42 None
+               OpSwitch %38 %41 1 %39 2 %40
+         %41 = OpLabel
+               OpStore %8 %36
+               OpBranch %42
+         %39 = OpLabel
+               OpBranch %42
+         %40 = OpLabel
+               OpStore %8 %32
+               OpStore %11 %29
+               OpBranch %41
+         %42 = OpLabel
+               OpBranch %22
+         %18 = OpLabel
+         %23 = OpLoad %6 %8
+               OpBranch %63
+         %63 = OpLabel
+         %24 = OpLoad %6 %11
+         %25 = OpIEqual %13 %23 %24
+               OpSelectionMerge %27 None
+               OpBranchConditional %25 %26 %27
+         %26 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+               OpStore %8 %29
+               OpBranch %22
+         %19 = OpLabel
+         %31 = OpLoad %6 %11
+         %33 = OpIEqual %13 %31 %32
+               OpSelectionMerge %35 None
+               OpBranchConditional %33 %34 %35
+         %34 = OpLabel
+               OpStore %11 %29
+               OpBranch %62
+         %62 = OpLabel
+               OpStore %8 %36
+               OpBranch %35
+         %35 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %16
+         %47 = OpLabel
+         %48 = OpLoad %6 %11
+               OpSelectionMerge %52 None
+               OpSwitch %48 %51 1 %49 2 %50
+         %51 = OpLabel
+         %53 = OpLoad %6 %11
+               OpStore %8 %53
+               OpBranch %52
+         %49 = OpLabel
+               OpStore %8 %32
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %11 %36
+               OpBranch %51
+         %52 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // The header and merge blocks
+  const uint32_t header_outer_if = 5;
+  const uint32_t merge_outer_if = 16;
+  const uint32_t header_then_outer_switch = 15;
+  const uint32_t merge_then_outer_switch = 22;
+  const uint32_t header_then_inner_switch = 21;
+  const uint32_t merge_then_inner_switch = 42;
+  const uint32_t header_else_switch = 47;
+  const uint32_t merge_else_switch = 52;
+  const uint32_t header_inner_if_1 = 19;
+  const uint32_t merge_inner_if_1 = 35;
+  const uint32_t header_inner_if_2 = 63;
+  const uint32_t merge_inner_if_2 = 27;
+
+  // The non-merge-nor-header blocks in each construct
+  const uint32_t then_outer_switch_block_1 = 18;
+  const uint32_t then_inner_switch_block_1 = 39;
+  const uint32_t then_inner_switch_block_2 = 40;
+  const uint32_t then_inner_switch_block_3 = 41;
+  const uint32_t else_switch_block_1 = 49;
+  const uint32_t else_switch_block_2 = 50;
+  const uint32_t else_switch_block_3 = 51;
+  const uint32_t inner_if_1_block_1 = 34;
+  const uint32_t inner_if_1_block_2 = 62;
+  const uint32_t inner_if_2_block_1 = 26;
+
+  // Fine to branch straight to direct merge block for a construct
+  ASSERT_TRUE(TransformationAddDeadBreak(then_outer_switch_block_1,
+                                         merge_then_outer_switch, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_1,
+                                         merge_then_inner_switch, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_2,
+                                         merge_then_inner_switch, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(then_inner_switch_block_3,
+                                         merge_then_inner_switch, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_1, merge_else_switch,
+                                         false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_2, merge_else_switch,
+                                         true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(else_switch_block_3, merge_else_switch,
+                                         false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(inner_if_1_block_1, merge_inner_if_1, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(inner_if_1_block_2, merge_inner_if_1,
+                                         false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(inner_if_2_block_1, merge_inner_if_2, true, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break out of a switch from a selection construct inside the
+  // switch.
+  ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_1,
+                                          merge_then_outer_switch, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(inner_if_1_block_2,
+                                          merge_then_outer_switch, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(inner_if_2_block_1,
+                                          merge_then_outer_switch, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Some miscellaneous inapplicable cases.
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(header_outer_if, merge_outer_if, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_1, inner_if_1_block_2,
+                                          false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_then_inner_switch,
+                                          header_then_outer_switch, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_else_switch,
+                                          then_inner_switch_block_3, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(header_inner_if_2, header_inner_if_2,
+                                          false, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto transformation1 = TransformationAddDeadBreak(
+      then_outer_switch_block_1, merge_then_outer_switch, true, {});
+  auto transformation2 = TransformationAddDeadBreak(
+      then_inner_switch_block_1, merge_then_inner_switch, false, {});
+  auto transformation3 = TransformationAddDeadBreak(
+      then_inner_switch_block_2, merge_then_inner_switch, true, {});
+  auto transformation4 = TransformationAddDeadBreak(
+      then_inner_switch_block_3, merge_then_inner_switch, true, {});
+  auto transformation5 = TransformationAddDeadBreak(
+      else_switch_block_1, merge_else_switch, false, {});
+  auto transformation6 = TransformationAddDeadBreak(
+      else_switch_block_2, merge_else_switch, true, {});
+  auto transformation7 = TransformationAddDeadBreak(
+      else_switch_block_3, merge_else_switch, false, {});
+  auto transformation8 = TransformationAddDeadBreak(inner_if_1_block_1,
+                                                    merge_inner_if_1, true, {});
+  auto transformation9 = TransformationAddDeadBreak(
+      inner_if_1_block_2, merge_inner_if_1, false, {});
+  auto transformation10 = TransformationAddDeadBreak(
+      inner_if_2_block_1, merge_inner_if_2, true, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
+  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation8.IsApplicable(context.get(), fact_manager));
+  transformation8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation9.IsApplicable(context.get(), fact_manager));
+  transformation9.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation10.IsApplicable(context.get(), fact_manager));
+  transformation10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpTypeBool
+         %29 = OpConstant %6 2
+         %32 = OpConstant %6 4
+         %36 = OpConstant %6 3
+         %60 = OpConstantTrue %13
+         %61 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %47
+         %15 = OpLabel
+         %17 = OpLoad %6 %8
+               OpSelectionMerge %22 None
+               OpSwitch %17 %21 0 %18 1 %18 3 %19 10 %20
+         %21 = OpLabel
+         %38 = OpLoad %6 %11
+               OpSelectionMerge %42 None
+               OpSwitch %38 %41 1 %39 2 %40
+         %41 = OpLabel
+               OpStore %8 %36
+               OpBranchConditional %60 %42 %42
+         %39 = OpLabel
+               OpBranchConditional %61 %42 %42
+         %40 = OpLabel
+               OpStore %8 %32
+               OpStore %11 %29
+               OpBranchConditional %60 %41 %42
+         %42 = OpLabel
+               OpBranch %22
+         %18 = OpLabel
+         %23 = OpLoad %6 %8
+               OpBranchConditional %60 %63 %22
+         %63 = OpLabel
+         %24 = OpLoad %6 %11
+         %25 = OpIEqual %13 %23 %24
+               OpSelectionMerge %27 None
+               OpBranchConditional %25 %26 %27
+         %26 = OpLabel
+               OpBranchConditional %60 %27 %27
+         %27 = OpLabel
+               OpStore %8 %29
+               OpBranch %22
+         %19 = OpLabel
+         %31 = OpLoad %6 %11
+         %33 = OpIEqual %13 %31 %32
+               OpSelectionMerge %35 None
+               OpBranchConditional %33 %34 %35
+         %34 = OpLabel
+               OpStore %11 %29
+               OpBranchConditional %60 %62 %35
+         %62 = OpLabel
+               OpStore %8 %36
+               OpBranchConditional %61 %35 %35
+         %35 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %16
+         %47 = OpLabel
+         %48 = OpLoad %6 %11
+               OpSelectionMerge %52 None
+               OpSwitch %48 %51 1 %49 2 %50
+         %51 = OpLabel
+         %53 = OpLoad %6 %11
+               OpStore %8 %53
+               OpBranchConditional %61 %52 %52
+         %49 = OpLabel
+               OpStore %8 %32
+               OpBranchConditional %61 %52 %50
+         %50 = OpLabel
+               OpStore %11 %36
+               OpBranchConditional %60 %51 %52
+         %52 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, BreakOutOfLoopNest) {
+  // Checks some allowed and disallowed scenarios for a nest of loops, including
+  // breaking from an if or switch right out of a loop.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   int x, y;
+  //   do {
+  //     x++;
+  //     for (int j = 0; j < 100; j++) {
+  //       y++;
+  //       if (x == y) {
+  //         x++;
+  //         if (x == 2) {
+  //           y++;
+  //         }
+  //         switch (x) {
+  //           case 0:
+  //             x = 2;
+  //           default:
+  //             break;
+  //         }
+  //       }
+  //     }
+  //   } while (x > y);
+  //
+  //   for (int i = 0; i < 100; i++) {
+  //     x++;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "x"
+               OpName %16 "j"
+               OpName %27 "y"
+               OpName %55 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %14 = OpConstant %10 1
+         %17 = OpConstant %10 0
+         %24 = OpConstant %10 100
+         %25 = OpTypeBool
+         %38 = OpConstant %10 2
+         %67 = OpConstantTrue %25
+         %68 = OpConstantFalse %25
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %11 Function
+         %27 = OpVariable %11 Function
+         %55 = OpVariable %11 Function
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranch %7
+          %7 = OpLabel
+         %13 = OpLoad %10 %12
+         %15 = OpIAdd %10 %13 %14
+               OpStore %12 %15
+               OpStore %16 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpLoad %10 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %28 = OpLoad %10 %27
+         %29 = OpIAdd %10 %28 %14
+               OpStore %27 %29
+         %30 = OpLoad %10 %12
+         %31 = OpLoad %10 %27
+         %32 = OpIEqual %25 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+         %35 = OpLoad %10 %12
+         %36 = OpIAdd %10 %35 %14
+               OpStore %12 %36
+         %37 = OpLoad %10 %12
+         %39 = OpIEqual %25 %37 %38
+               OpSelectionMerge %41 None
+               OpBranchConditional %39 %40 %41
+         %40 = OpLabel
+         %42 = OpLoad %10 %27
+         %43 = OpIAdd %10 %42 %14
+               OpStore %27 %43
+               OpBranch %41
+         %41 = OpLabel
+         %44 = OpLoad %10 %12
+               OpSelectionMerge %47 None
+               OpSwitch %44 %46 0 %45
+         %46 = OpLabel
+               OpBranch %47
+         %45 = OpLabel
+               OpStore %12 %38
+               OpBranch %46
+         %47 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+         %50 = OpLoad %10 %16
+         %51 = OpIAdd %10 %50 %14
+               OpStore %16 %51
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %52 = OpLoad %10 %12
+         %53 = OpLoad %10 %27
+         %54 = OpSGreaterThan %25 %52 %53
+               OpBranchConditional %54 %6 %8
+          %8 = OpLabel
+               OpStore %55 %17
+               OpBranch %56
+         %56 = OpLabel
+               OpLoopMerge %58 %59 None
+               OpBranch %60
+         %60 = OpLabel
+         %61 = OpLoad %10 %55
+         %62 = OpSLessThan %25 %61 %24
+               OpBranchConditional %62 %57 %58
+         %57 = OpLabel
+         %63 = OpLoad %10 %12
+         %64 = OpIAdd %10 %63 %14
+               OpStore %12 %64
+               OpBranch %59
+         %59 = OpLabel
+         %65 = OpLoad %10 %55
+         %66 = OpIAdd %10 %65 %14
+               OpStore %55 %66
+               OpBranch %56
+         %58 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // The header and merge blocks
+  const uint32_t header_do_while = 6;
+  const uint32_t merge_do_while = 8;
+  const uint32_t header_for_j = 18;
+  const uint32_t merge_for_j = 20;
+  const uint32_t header_for_i = 56;
+  const uint32_t merge_for_i = 58;
+  const uint32_t header_switch = 41;
+  const uint32_t merge_switch = 47;
+  const uint32_t header_if_x_eq_y = 19;
+  const uint32_t merge_if_x_eq_y = 34;
+  const uint32_t header_if_x_eq_2 = 33;
+  const uint32_t merge_if_x_eq_2 = 41;
+
+  // Loop continue targets
+  const uint32_t continue_do_while = 9;
+  const uint32_t continue_for_j = 21;
+  const uint32_t continue_for_i = 59;
+
+  // Some blocks in these constructs
+  const uint32_t block_in_inner_if = 40;
+  const uint32_t block_switch_case = 46;
+  const uint32_t block_switch_default = 45;
+  const uint32_t block_in_for_i_loop = 57;
+
+  // Fine to break from any loop header to its merge
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(header_do_while, merge_do_while, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(header_for_i, merge_for_i, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadBreak(header_for_j, merge_for_j, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Fine to break from any of the blocks in constructs in the "for j" loop to
+  // that loop's merge
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(block_in_inner_if, merge_for_j, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(block_switch_case, merge_for_j, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(block_switch_default, merge_for_j, false, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Fine to break from the body of the "for i" loop to that loop's merge
+  ASSERT_TRUE(
+      TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break from multiple loops
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(block_in_inner_if, merge_do_while, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(block_switch_case, merge_do_while, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_do_while,
+                                          false, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(header_for_j, merge_do_while, true, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break loop from its continue construct
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(continue_do_while, merge_do_while, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(continue_for_j, merge_for_j, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(continue_for_i, merge_for_i, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Not OK to break out of multiple non-loop constructs if not breaking to a
+  // loop merge
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(block_in_inner_if, merge_if_x_eq_y, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(block_switch_case, merge_if_x_eq_y, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(block_switch_default, merge_if_x_eq_y,
+                                          false, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Some miscellaneous inapplicable transformations
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(header_if_x_eq_2, header_if_x_eq_y, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(merge_if_x_eq_2, merge_switch, false, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(header_switch, header_switch, false, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  auto transformation1 =
+      TransformationAddDeadBreak(header_do_while, merge_do_while, true, {});
+  auto transformation2 =
+      TransformationAddDeadBreak(header_for_i, merge_for_i, false, {});
+  auto transformation3 =
+      TransformationAddDeadBreak(header_for_j, merge_for_j, true, {});
+  auto transformation4 =
+      TransformationAddDeadBreak(block_in_inner_if, merge_for_j, false, {});
+  auto transformation5 =
+      TransformationAddDeadBreak(block_switch_case, merge_for_j, true, {});
+  auto transformation6 =
+      TransformationAddDeadBreak(block_switch_default, merge_for_j, false, {});
+  auto transformation7 =
+      TransformationAddDeadBreak(block_in_for_i_loop, merge_for_i, true, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+  transformation6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
+  transformation7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "x"
+               OpName %16 "j"
+               OpName %27 "y"
+               OpName %55 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %14 = OpConstant %10 1
+         %17 = OpConstant %10 0
+         %24 = OpConstant %10 100
+         %25 = OpTypeBool
+         %38 = OpConstant %10 2
+         %67 = OpConstantTrue %25
+         %68 = OpConstantFalse %25
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %11 Function
+         %27 = OpVariable %11 Function
+         %55 = OpVariable %11 Function
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranchConditional %67 %7 %8
+          %7 = OpLabel
+         %13 = OpLoad %10 %12
+         %15 = OpIAdd %10 %13 %14
+               OpStore %12 %15
+               OpStore %16 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranchConditional %67 %22 %20
+         %22 = OpLabel
+         %23 = OpLoad %10 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %28 = OpLoad %10 %27
+         %29 = OpIAdd %10 %28 %14
+               OpStore %27 %29
+         %30 = OpLoad %10 %12
+         %31 = OpLoad %10 %27
+         %32 = OpIEqual %25 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+         %35 = OpLoad %10 %12
+         %36 = OpIAdd %10 %35 %14
+               OpStore %12 %36
+         %37 = OpLoad %10 %12
+         %39 = OpIEqual %25 %37 %38
+               OpSelectionMerge %41 None
+               OpBranchConditional %39 %40 %41
+         %40 = OpLabel
+         %42 = OpLoad %10 %27
+         %43 = OpIAdd %10 %42 %14
+               OpStore %27 %43
+               OpBranchConditional %68 %20 %41
+         %41 = OpLabel
+         %44 = OpLoad %10 %12
+               OpSelectionMerge %47 None
+               OpSwitch %44 %46 0 %45
+         %46 = OpLabel
+               OpBranchConditional %67 %47 %20
+         %45 = OpLabel
+               OpStore %12 %38
+               OpBranchConditional %68 %20 %46
+         %47 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+         %50 = OpLoad %10 %16
+         %51 = OpIAdd %10 %50 %14
+               OpStore %16 %51
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %52 = OpLoad %10 %12
+         %53 = OpLoad %10 %27
+         %54 = OpSGreaterThan %25 %52 %53
+               OpBranchConditional %54 %6 %8
+          %8 = OpLabel
+               OpStore %55 %17
+               OpBranch %56
+         %56 = OpLabel
+               OpLoopMerge %58 %59 None
+               OpBranchConditional %68 %58 %60
+         %60 = OpLabel
+         %61 = OpLoad %10 %55
+         %62 = OpSLessThan %25 %61 %24
+               OpBranchConditional %62 %57 %58
+         %57 = OpLabel
+         %63 = OpLoad %10 %12
+         %64 = OpIAdd %10 %63 %14
+               OpStore %12 %64
+               OpBranchConditional %67 %59 %58
+         %59 = OpLabel
+         %65 = OpLoad %10 %55
+         %66 = OpIAdd %10 %65 %14
+               OpStore %55 %66
+               OpBranch %56
+         %58 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, NoBreakFromContinueConstruct) {
+  // Checks that it is illegal to break straight from a continue construct.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   for (int i = 0; i < 100; i++) {
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %22 = OpConstantTrue %17
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpBranch %23
+         %23 = OpLabel
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Not OK to break loop from its continue construct
+  ASSERT_FALSE(TransformationAddDeadBreak(13, 12, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 12, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationAddDeadBreakTest, SelectionInContinueConstruct) {
+  // Considers some scenarios where there is a selection construct in a loop's
+  // continue construct.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   for (int i = 0; i < 100; i = (i < 50 ? i + 2 : i + 1)) {
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %99 = OpConstantTrue %17
+         %20 = OpConstant %6 50
+         %26 = OpConstant %6 2
+         %30 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpSLessThan %17 %19 %20
+               OpSelectionMerge %24 None
+               OpBranchConditional %21 %23 %28
+         %23 = OpLabel
+         %25 = OpLoad %6 %8
+               OpBranch %100
+        %100 = OpLabel
+         %27 = OpIAdd %6 %25 %26
+               OpStore %22 %27
+               OpBranch %24
+         %28 = OpLabel
+         %29 = OpLoad %6 %8
+               OpBranch %101
+        %101 = OpLabel
+         %31 = OpIAdd %6 %29 %30
+               OpStore %22 %31
+               OpBranch %24
+         %24 = OpLabel
+         %32 = OpLoad %6 %22
+               OpStore %8 %32
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  const uint32_t loop_merge = 12;
+  const uint32_t selection_merge = 24;
+  const uint32_t in_selection_1 = 23;
+  const uint32_t in_selection_2 = 100;
+  const uint32_t in_selection_3 = 28;
+  const uint32_t in_selection_4 = 101;
+
+  // Not OK to jump from the selection to the loop merge, as this would break
+  // from the loop's continue construct.
+  ASSERT_FALSE(TransformationAddDeadBreak(in_selection_1, loop_merge, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(in_selection_2, loop_merge, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(in_selection_3, loop_merge, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadBreak(in_selection_4, loop_merge, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // But fine to jump from the selection to its merge.
+
+  auto transformation1 =
+      TransformationAddDeadBreak(in_selection_1, selection_merge, true, {});
+  auto transformation2 =
+      TransformationAddDeadBreak(in_selection_2, selection_merge, true, {});
+  auto transformation3 =
+      TransformationAddDeadBreak(in_selection_3, selection_merge, true, {});
+  auto transformation4 =
+      TransformationAddDeadBreak(in_selection_4, selection_merge, true, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %99 = OpConstantTrue %17
+         %20 = OpConstant %6 50
+         %26 = OpConstant %6 2
+         %30 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpSLessThan %17 %19 %20
+               OpSelectionMerge %24 None
+               OpBranchConditional %21 %23 %28
+         %23 = OpLabel
+         %25 = OpLoad %6 %8
+               OpBranchConditional %99 %100 %24
+        %100 = OpLabel
+         %27 = OpIAdd %6 %25 %26
+               OpStore %22 %27
+               OpBranchConditional %99 %24 %24
+         %28 = OpLabel
+         %29 = OpLoad %6 %8
+               OpBranchConditional %99 %101 %24
+        %101 = OpLabel
+         %31 = OpIAdd %6 %29 %30
+               OpStore %22 %31
+               OpBranchConditional %99 %24 %24
+         %24 = OpLabel
+         %32 = OpLoad %6 %22
+               OpStore %8 %32
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, LoopInContinueConstruct) {
+  // Considers some scenarios where there is a loop in a loop's continue
+  // construct.
+
+  // The SPIR-V for this test is adapted from the following GLSL, with inlining
+  // applied so that the loop from foo is in the main loop's continue construct:
+  //
+  // int foo() {
+  //   int result = 0;
+  //   for (int j = 0; j < 10; j++) {
+  //     result++;
+  //   }
+  //   return result;
+  // }
+  //
+  // void main() {
+  //   for (int i = 0; i < 100; i += foo()) {
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %31 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %20 = OpConstant %6 10
+         %21 = OpTypeBool
+        %100 = OpConstantTrue %21
+         %24 = OpConstant %6 1
+         %38 = OpConstant %6 100
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %43 = OpVariable %10 Function
+         %44 = OpVariable %10 Function
+         %45 = OpVariable %10 Function
+         %31 = OpVariable %10 Function
+               OpStore %31 %12
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %34 %35 None
+               OpBranch %36
+         %36 = OpLabel
+         %37 = OpLoad %6 %31
+         %39 = OpSLessThan %21 %37 %38
+               OpBranchConditional %39 %33 %34
+         %33 = OpLabel
+               OpBranch %35
+         %35 = OpLabel
+               OpStore %43 %12
+               OpStore %44 %12
+               OpBranch %46
+         %46 = OpLabel
+               OpLoopMerge %47 %48 None
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %6 %44
+         %51 = OpSLessThan %21 %50 %20
+               OpBranchConditional %51 %52 %47
+         %52 = OpLabel
+         %53 = OpLoad %6 %43
+               OpBranch %101
+        %101 = OpLabel
+         %54 = OpIAdd %6 %53 %24
+               OpStore %43 %54
+               OpBranch %48
+         %48 = OpLabel
+         %55 = OpLoad %6 %44
+         %56 = OpIAdd %6 %55 %24
+               OpStore %44 %56
+               OpBranch %46
+         %47 = OpLabel
+         %57 = OpLoad %6 %43
+               OpStore %45 %57
+         %40 = OpLoad %6 %45
+         %41 = OpLoad %6 %31
+         %42 = OpIAdd %6 %41 %40
+               OpStore %31 %42
+               OpBranch %32
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  const uint32_t outer_loop_merge = 34;
+  const uint32_t outer_loop_block = 33;
+  const uint32_t inner_loop_merge = 47;
+  const uint32_t inner_loop_block = 52;
+
+  // Some inapplicable cases
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(inner_loop_block, outer_loop_merge, true, {})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationAddDeadBreak(outer_loop_block, inner_loop_merge, true, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  auto transformation1 =
+      TransformationAddDeadBreak(inner_loop_block, inner_loop_merge, true, {});
+  auto transformation2 =
+      TransformationAddDeadBreak(outer_loop_block, outer_loop_merge, true, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %31 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %20 = OpConstant %6 10
+         %21 = OpTypeBool
+        %100 = OpConstantTrue %21
+         %24 = OpConstant %6 1
+         %38 = OpConstant %6 100
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %43 = OpVariable %10 Function
+         %44 = OpVariable %10 Function
+         %45 = OpVariable %10 Function
+         %31 = OpVariable %10 Function
+               OpStore %31 %12
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %34 %35 None
+               OpBranch %36
+         %36 = OpLabel
+         %37 = OpLoad %6 %31
+         %39 = OpSLessThan %21 %37 %38
+               OpBranchConditional %39 %33 %34
+         %33 = OpLabel
+               OpBranchConditional %100 %35 %34
+         %35 = OpLabel
+               OpStore %43 %12
+               OpStore %44 %12
+               OpBranch %46
+         %46 = OpLabel
+               OpLoopMerge %47 %48 None
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %6 %44
+         %51 = OpSLessThan %21 %50 %20
+               OpBranchConditional %51 %52 %47
+         %52 = OpLabel
+         %53 = OpLoad %6 %43
+               OpBranchConditional %100 %101 %47
+        %101 = OpLabel
+         %54 = OpIAdd %6 %53 %24
+               OpStore %43 %54
+               OpBranch %48
+         %48 = OpLabel
+         %55 = OpLoad %6 %44
+         %56 = OpIAdd %6 %55 %24
+               OpStore %44 %56
+               OpBranch %46
+         %47 = OpLabel
+         %57 = OpLoad %6 %43
+               OpStore %45 %57
+         %40 = OpLoad %6 %45
+         %41 = OpLoad %6 %31
+         %42 = OpIAdd %6 %41 %40
+               OpStore %31 %42
+               OpBranch %32
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadBreakTest, PhiInstructions) {
+  // Checks that the transformation works in the presence of phi instructions.
+
+  // The SPIR-V for this test is adapted from the following GLSL, with a bit of
+  // extra and artificial work to get some interesting uses of OpPhi:
+  //
+  // void main() {
+  //   int x; int y;
+  //   float f;
+  //   x = 2;
+  //   f = 3.0;
+  //   if (x > y) {
+  //     x = 3;
+  //     f = 4.0;
+  //   } else {
+  //     x = x + 2;
+  //     f = f + 10.0;
+  //   }
+  //   while (x < y) {
+  //     x = x + 1;
+  //     f = f + 1.0;
+  //   }
+  //   y = x;
+  //   f = f + 3.0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "f"
+               OpName %15 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %10 = OpTypeFloat 32
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 3
+         %17 = OpTypeBool
+         %80 = OpConstantTrue %17
+         %21 = OpConstant %6 3
+         %22 = OpConstant %10 4
+         %27 = OpConstant %10 10
+         %38 = OpConstant %6 1
+         %41 = OpConstant %10 1
+         %46 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %15 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %12 %13
+         %18 = OpSGreaterThan %17 %9 %46
+               OpSelectionMerge %20 None
+               OpBranchConditional %18 %19 %23
+         %19 = OpLabel
+               OpStore %8 %21
+               OpStore %12 %22
+               OpBranch %20
+         %23 = OpLabel
+         %25 = OpIAdd %6 %9 %9
+               OpStore %8 %25
+               OpBranch %70
+         %70 = OpLabel
+         %28 = OpFAdd %10 %13 %27
+               OpStore %12 %28
+               OpBranch %20
+         %20 = OpLabel
+         %52 = OpPhi %10 %22 %19 %28 %70
+         %48 = OpPhi %6 %21 %19 %25 %70
+               OpBranch %29
+         %29 = OpLabel
+         %51 = OpPhi %10 %52 %20 %42 %32
+         %47 = OpPhi %6 %48 %20 %39 %32
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+         %36 = OpSLessThan %17 %47 %46
+               OpBranchConditional %36 %30 %31
+         %30 = OpLabel
+         %39 = OpIAdd %6 %47 %38
+               OpStore %8 %39
+               OpBranch %75
+         %75 = OpLabel
+         %42 = OpFAdd %10 %51 %41
+               OpStore %12 %42
+               OpBranch %32
+         %32 = OpLabel
+               OpBranch %29
+         %31 = OpLabel
+         %71 = OpPhi %6 %47 %33
+         %72 = OpPhi %10 %51 %33
+               OpStore %15 %71
+         %45 = OpFAdd %10 %72 %13
+               OpStore %12 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Some inapplicable transformations
+  // Not applicable because there is already an edge 19->20, so the OpPhis at 20
+  // do not need to be updated
+  ASSERT_FALSE(TransformationAddDeadBreak(19, 20, true, {13, 21})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because two OpPhis (not zero) need to be updated at 20
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because two OpPhis (not just one) need to be updated at 20
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {13})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because only two OpPhis (not three) need to be updated at 20
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {13, 21, 22})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because the given ids do not have types that match the
+  // OpPhis at 20, in order
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 13})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because id 23 is a label
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 23})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because 101 is not an id
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {21, 101})
+                   .IsApplicable(context.get(), fact_manager));
+  // Not applicable because ids 51 and 47 are not available at the end of block
+  // 23
+  ASSERT_FALSE(TransformationAddDeadBreak(23, 20, true, {51, 47})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Not applicable because OpConstantFalse is not present in the module
+  ASSERT_FALSE(TransformationAddDeadBreak(19, 20, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto transformation1 = TransformationAddDeadBreak(19, 20, true, {});
+  auto transformation2 = TransformationAddDeadBreak(23, 20, true, {13, 21});
+  auto transformation3 = TransformationAddDeadBreak(70, 20, true, {});
+  auto transformation4 = TransformationAddDeadBreak(30, 31, true, {21, 13});
+  auto transformation5 = TransformationAddDeadBreak(75, 31, true, {47, 51});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "f"
+               OpName %15 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %10 = OpTypeFloat 32
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 3
+         %17 = OpTypeBool
+         %80 = OpConstantTrue %17
+         %21 = OpConstant %6 3
+         %22 = OpConstant %10 4
+         %27 = OpConstant %10 10
+         %38 = OpConstant %6 1
+         %41 = OpConstant %10 1
+         %46 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %15 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %12 %13
+         %18 = OpSGreaterThan %17 %9 %46
+               OpSelectionMerge %20 None
+               OpBranchConditional %18 %19 %23
+         %19 = OpLabel
+               OpStore %8 %21
+               OpStore %12 %22
+               OpBranchConditional %80 %20 %20
+         %23 = OpLabel
+         %25 = OpIAdd %6 %9 %9
+               OpStore %8 %25
+               OpBranchConditional %80 %70 %20
+         %70 = OpLabel
+         %28 = OpFAdd %10 %13 %27
+               OpStore %12 %28
+               OpBranchConditional %80 %20 %20
+         %20 = OpLabel
+         %52 = OpPhi %10 %22 %19 %28 %70 %13 %23
+         %48 = OpPhi %6 %21 %19 %25 %70 %21 %23
+               OpBranch %29
+         %29 = OpLabel
+         %51 = OpPhi %10 %52 %20 %42 %32
+         %47 = OpPhi %6 %48 %20 %39 %32
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+         %36 = OpSLessThan %17 %47 %46
+               OpBranchConditional %36 %30 %31
+         %30 = OpLabel
+         %39 = OpIAdd %6 %47 %38
+               OpStore %8 %39
+               OpBranchConditional %80 %75 %31
+         %75 = OpLabel
+         %42 = OpFAdd %10 %51 %41
+               OpStore %12 %42
+               OpBranchConditional %80 %32 %31
+         %32 = OpLabel
+               OpBranch %29
+         %31 = OpLabel
+         %71 = OpPhi %6 %47 %33 %21 %30 %47 %75
+         %72 = OpPhi %10 %51 %33 %13 %30 %51 %75
+               OpStore %15 %71
+         %45 = OpFAdd %10 %72 %13
+               OpStore %12 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_dead_continue_test.cpp b/test/fuzz/transformation_add_dead_continue_test.cpp
new file mode 100644
index 0000000..573db19
--- /dev/null
+++ b/test/fuzz/transformation_add_dead_continue_test.cpp
@@ -0,0 +1,1034 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_dead_continue.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddDeadContinueTest, SimpleExample) {
+  // For a simple loop, checks that some dead continue scenarios are possible,
+  // sanity-checks that some illegal scenarios are indeed not allowed, and then
+  // applies a transformation.
+
+  // The SPIR-V for this test is adapted from the following GLSL, by separating
+  // some assignments into their own basic blocks, and adding constants for true
+  // and false:
+  //
+  // void main() {
+  //   int x = 0;
+  //   for (int i = 0; i < 10; i++) {
+  //     x = x + i;
+  //     x = x + i;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %17 = OpConstant %6 10
+         %18 = OpTypeBool
+         %41 = OpConstantTrue %18
+         %42 = OpConstantFalse %18
+         %27 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %9
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %13 %14 None
+               OpBranch %15
+         %15 = OpLabel
+         %16 = OpLoad %6 %10
+         %19 = OpSLessThan %18 %16 %17
+               OpBranchConditional %19 %12 %13
+         %12 = OpLabel
+         %20 = OpLoad %6 %8
+         %21 = OpLoad %6 %10
+         %22 = OpIAdd %6 %20 %21
+               OpStore %8 %22
+               OpBranch %40
+         %40 = OpLabel
+         %23 = OpLoad %6 %8
+         %24 = OpLoad %6 %10
+         %25 = OpIAdd %6 %23 %24
+               OpStore %8 %25
+               OpBranch %14
+         %14 = OpLabel
+         %26 = OpLoad %6 %10
+         %28 = OpIAdd %6 %26 %27
+               OpStore %10 %28
+               OpBranch %11
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+
+  // These are all possibilities.
+  ASSERT_TRUE(TransformationAddDeadContinue(11, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadContinue(11, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadContinue(12, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadContinue(12, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadContinue(40, true, {})
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationAddDeadContinue(40, false, {})
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 100 is not a block id.
+  ASSERT_FALSE(TransformationAddDeadContinue(100, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 10 is not in a loop.
+  ASSERT_FALSE(TransformationAddDeadContinue(10, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 15 does not branch unconditionally to a single successor.
+  ASSERT_FALSE(TransformationAddDeadContinue(15, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 13 is not in a loop and has no successor.
+  ASSERT_FALSE(TransformationAddDeadContinue(13, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable: 14 is the loop continue target, so it's not OK to jump to
+  // the loop continue from there.
+  ASSERT_FALSE(TransformationAddDeadContinue(14, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // These are the transformations we will apply.
+  auto transformation1 = TransformationAddDeadContinue(11, true, {});
+  auto transformation2 = TransformationAddDeadContinue(12, false, {});
+  auto transformation3 = TransformationAddDeadContinue(40, true, {});
+
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %17 = OpConstant %6 10
+         %18 = OpTypeBool
+         %41 = OpConstantTrue %18
+         %42 = OpConstantFalse %18
+         %27 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %9
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %13 %14 None
+               OpBranchConditional %41 %15 %14
+         %15 = OpLabel
+         %16 = OpLoad %6 %10
+         %19 = OpSLessThan %18 %16 %17
+               OpBranchConditional %19 %12 %13
+         %12 = OpLabel
+         %20 = OpLoad %6 %8
+         %21 = OpLoad %6 %10
+         %22 = OpIAdd %6 %20 %21
+               OpStore %8 %22
+               OpBranchConditional %42 %14 %40
+         %40 = OpLabel
+         %23 = OpLoad %6 %8
+         %24 = OpLoad %6 %10
+         %25 = OpIAdd %6 %23 %24
+               OpStore %8 %25
+               OpBranchConditional %41 %14 %14
+         %14 = OpLabel
+         %26 = OpLoad %6 %10
+         %28 = OpIAdd %6 %26 %27
+               OpStore %10 %28
+               OpBranch %11
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadContinueTest,
+     DoNotAllowContinueToMergeBlockOfAnotherLoop) {
+  // A loop header must dominate its merge block if that merge block is
+  // reachable. We are thus not allowed to add a dead continue that would result
+  // in violation of this property. This test checks for such a scenario.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %16 %139
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeBool
+         %14 = OpTypeVector %6 4
+         %15 = OpTypePointer Input %14
+         %16 = OpVariable %15 Input
+        %138 = OpTypePointer Output %14
+        %139 = OpVariable %138 Output
+        %400 = OpConstantTrue %8
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %500
+        %500 = OpLabel
+               OpLoopMerge %501 %502 None
+               OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502
+        %503 = OpLabel
+               OpLoopMerge %502 %504 None
+               OpBranchConditional %400 %505 %504
+        %505 = OpLabel
+               OpBranch %502
+        %504 = OpLabel
+               OpBranch %503
+        %502 = OpLabel
+               OpBranchConditional %400 %501 %500
+        %501 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+
+  ASSERT_FALSE(TransformationAddDeadContinue(500, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadContinue(500, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationAddDeadContinueTest, DoNotAllowContinueToSelectionMerge) {
+  // A selection header must dominate its merge block if that merge block is
+  // reachable. We are thus not allowed to add a dead continue that would result
+  // in violation of this property. This test checks for such a scenario.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %16 %139
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeBool
+         %14 = OpTypeVector %6 4
+         %15 = OpTypePointer Input %14
+         %16 = OpVariable %15 Input
+        %138 = OpTypePointer Output %14
+        %139 = OpVariable %138 Output
+        %400 = OpConstantTrue %8
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %500
+        %500 = OpLabel
+               OpLoopMerge %501 %502 None
+               OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502
+        %503 = OpLabel
+               OpSelectionMerge %502 None
+               OpBranchConditional %400 %505 %504
+        %505 = OpLabel
+               OpBranch %502
+        %504 = OpLabel
+               OpBranch %502
+        %502 = OpLabel
+               OpBranchConditional %400 %501 %500
+        %501 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+
+  ASSERT_FALSE(TransformationAddDeadContinue(500, true, {})
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationAddDeadContinue(500, false, {})
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationAddDeadContinueTest, LoopNest) {
+  // Checks some allowed and disallowed scenarios for a nest of loops, including
+  // continuing a loop from an if or switch.
+
+  // The SPIR-V for this test is adapted from the following GLSL:
+  //
+  // void main() {
+  //   int x, y;
+  //   do {
+  //     x++;
+  //     for (int j = 0; j < 100; j++) {
+  //       y++;
+  //       if (x == y) {
+  //         x++;
+  //         if (x == 2) {
+  //           y++;
+  //         }
+  //         switch (x) {
+  //           case 0:
+  //             x = 2;
+  //           default:
+  //             break;
+  //         }
+  //       }
+  //     }
+  //   } while (x > y);
+  //
+  //   for (int i = 0; i < 100; i++) {
+  //     x++;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "x"
+               OpName %16 "j"
+               OpName %27 "y"
+               OpName %55 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %14 = OpConstant %10 1
+         %17 = OpConstant %10 0
+         %24 = OpConstant %10 100
+         %25 = OpTypeBool
+         %38 = OpConstant %10 2
+         %67 = OpConstantTrue %25
+         %68 = OpConstantFalse %25
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %11 Function
+         %27 = OpVariable %11 Function
+         %55 = OpVariable %11 Function
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranch %7
+          %7 = OpLabel
+         %13 = OpLoad %10 %12
+         %15 = OpIAdd %10 %13 %14
+               OpStore %12 %15
+               OpStore %16 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpLoad %10 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %28 = OpLoad %10 %27
+         %29 = OpIAdd %10 %28 %14
+               OpStore %27 %29
+         %30 = OpLoad %10 %12
+         %31 = OpLoad %10 %27
+         %32 = OpIEqual %25 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+         %35 = OpLoad %10 %12
+         %36 = OpIAdd %10 %35 %14
+               OpStore %12 %36
+         %37 = OpLoad %10 %12
+         %39 = OpIEqual %25 %37 %38
+               OpSelectionMerge %41 None
+               OpBranchConditional %39 %40 %41
+         %40 = OpLabel
+         %42 = OpLoad %10 %27
+         %43 = OpIAdd %10 %42 %14
+               OpStore %27 %43
+               OpBranch %41
+         %41 = OpLabel
+         %44 = OpLoad %10 %12
+               OpSelectionMerge %47 None
+               OpSwitch %44 %46 0 %45
+         %46 = OpLabel
+               OpBranch %47
+         %45 = OpLabel
+               OpStore %12 %38
+               OpBranch %46
+         %47 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+         %50 = OpLoad %10 %16
+         %51 = OpIAdd %10 %50 %14
+               OpStore %16 %51
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %52 = OpLoad %10 %12
+         %53 = OpLoad %10 %27
+         %54 = OpSGreaterThan %25 %52 %53
+               OpBranchConditional %54 %6 %8
+          %8 = OpLabel
+               OpStore %55 %17
+               OpBranch %56
+         %56 = OpLabel
+               OpLoopMerge %58 %59 None
+               OpBranch %60
+         %60 = OpLabel
+         %61 = OpLoad %10 %55
+         %62 = OpSLessThan %25 %61 %24
+               OpBranchConditional %62 %57 %58
+         %57 = OpLabel
+         %63 = OpLoad %10 %12
+         %64 = OpIAdd %10 %63 %14
+               OpStore %12 %64
+               OpBranch %59
+         %59 = OpLabel
+         %65 = OpLoad %10 %55
+         %66 = OpIAdd %10 %65 %14
+               OpStore %55 %66
+               OpBranch %56
+         %58 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  std::vector<uint32_t> good = {6, 7, 18, 20, 34, 40, 45, 46, 47, 56, 57};
+  std::vector<uint32_t> bad = {5, 8, 9, 19, 21, 22, 33, 41, 58, 59, 60};
+
+  for (uint32_t from_block : bad) {
+    ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
+                     .IsApplicable(context.get(), fact_manager));
+  }
+  for (uint32_t from_block : good) {
+    const TransformationAddDeadContinue transformation(from_block, true, {});
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "x"
+               OpName %16 "j"
+               OpName %27 "y"
+               OpName %55 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Function %10
+         %14 = OpConstant %10 1
+         %17 = OpConstant %10 0
+         %24 = OpConstant %10 100
+         %25 = OpTypeBool
+         %38 = OpConstant %10 2
+         %67 = OpConstantTrue %25
+         %68 = OpConstantFalse %25
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %11 Function
+         %16 = OpVariable %11 Function
+         %27 = OpVariable %11 Function
+         %55 = OpVariable %11 Function
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %9 None
+               OpBranchConditional %67 %7 %9
+          %7 = OpLabel
+         %13 = OpLoad %10 %12
+         %15 = OpIAdd %10 %13 %14
+               OpStore %12 %15
+               OpStore %16 %17
+               OpBranchConditional %67 %18 %9
+         %18 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranchConditional %67 %22 %21
+         %22 = OpLabel
+         %23 = OpLoad %10 %16
+         %26 = OpSLessThan %25 %23 %24
+               OpBranchConditional %26 %19 %20
+         %19 = OpLabel
+         %28 = OpLoad %10 %27
+         %29 = OpIAdd %10 %28 %14
+               OpStore %27 %29
+         %30 = OpLoad %10 %12
+         %31 = OpLoad %10 %27
+         %32 = OpIEqual %25 %30 %31
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+         %35 = OpLoad %10 %12
+         %36 = OpIAdd %10 %35 %14
+               OpStore %12 %36
+         %37 = OpLoad %10 %12
+         %39 = OpIEqual %25 %37 %38
+               OpSelectionMerge %41 None
+               OpBranchConditional %39 %40 %41
+         %40 = OpLabel
+         %42 = OpLoad %10 %27
+         %43 = OpIAdd %10 %42 %14
+               OpStore %27 %43
+               OpBranchConditional %67 %41 %21
+         %41 = OpLabel
+         %44 = OpLoad %10 %12
+               OpSelectionMerge %47 None
+               OpSwitch %44 %46 0 %45
+         %46 = OpLabel
+               OpBranchConditional %67 %47 %21
+         %45 = OpLabel
+               OpStore %12 %38
+               OpBranchConditional %67 %46 %21
+         %47 = OpLabel
+               OpBranchConditional %67 %34 %21
+         %34 = OpLabel
+               OpBranchConditional %67 %21 %21
+         %21 = OpLabel
+         %50 = OpLoad %10 %16
+         %51 = OpIAdd %10 %50 %14
+               OpStore %16 %51
+               OpBranch %18
+         %20 = OpLabel
+               OpBranchConditional %67 %9 %9
+          %9 = OpLabel
+         %52 = OpLoad %10 %12
+         %53 = OpLoad %10 %27
+         %54 = OpSGreaterThan %25 %52 %53
+               OpBranchConditional %54 %6 %8
+          %8 = OpLabel
+               OpStore %55 %17
+               OpBranch %56
+         %56 = OpLabel
+               OpLoopMerge %58 %59 None
+               OpBranchConditional %67 %60 %59
+         %60 = OpLabel
+         %61 = OpLoad %10 %55
+         %62 = OpSLessThan %25 %61 %24
+               OpBranchConditional %62 %57 %58
+         %57 = OpLabel
+         %63 = OpLoad %10 %12
+         %64 = OpIAdd %10 %63 %14
+               OpStore %12 %64
+               OpBranchConditional %67 %59 %59
+         %59 = OpLabel
+         %65 = OpLoad %10 %55
+         %66 = OpIAdd %10 %65 %14
+               OpStore %55 %66
+               OpBranch %56
+         %58 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadConditionalTest, LoopInContinueConstruct) {
+  // Considers some scenarios where there is a loop in a loop's continue
+  // construct.
+
+  // The SPIR-V for this test is adapted from the following GLSL, with inlining
+  // applied so that the loop from foo is in the main loop's continue construct:
+  //
+  // int foo() {
+  //   int result = 0;
+  //   for (int j = 0; j < 10; j++) {
+  //     result++;
+  //   }
+  //   return result;
+  // }
+  //
+  // void main() {
+  //   for (int i = 0; i < 100; i += foo()) {
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %31 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %20 = OpConstant %6 10
+         %21 = OpTypeBool
+        %100 = OpConstantFalse %21
+         %24 = OpConstant %6 1
+         %38 = OpConstant %6 100
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %43 = OpVariable %10 Function
+         %44 = OpVariable %10 Function
+         %45 = OpVariable %10 Function
+         %31 = OpVariable %10 Function
+               OpStore %31 %12
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %34 %35 None
+               OpBranch %36
+         %36 = OpLabel
+         %37 = OpLoad %6 %31
+         %39 = OpSLessThan %21 %37 %38
+               OpBranchConditional %39 %33 %34
+         %33 = OpLabel
+               OpBranch %35
+         %35 = OpLabel
+               OpStore %43 %12
+               OpStore %44 %12
+               OpBranch %46
+         %46 = OpLabel
+               OpLoopMerge %47 %48 None
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %6 %44
+         %51 = OpSLessThan %21 %50 %20
+               OpBranchConditional %51 %52 %47
+         %52 = OpLabel
+         %53 = OpLoad %6 %43
+               OpBranch %101
+        %101 = OpLabel
+         %54 = OpIAdd %6 %53 %24
+               OpStore %43 %54
+               OpBranch %48
+         %48 = OpLabel
+         %55 = OpLoad %6 %44
+         %56 = OpIAdd %6 %55 %24
+               OpStore %44 %56
+               OpBranch %46
+         %47 = OpLabel
+         %57 = OpLoad %6 %43
+               OpStore %45 %57
+         %40 = OpLoad %6 %45
+         %41 = OpLoad %6 %31
+         %42 = OpIAdd %6 %41 %40
+               OpStore %31 %42
+               OpBranch %32
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  std::vector<uint32_t> good = {32, 33, 46, 52, 101};
+  std::vector<uint32_t> bad = {5, 34, 36, 35, 47, 49, 48};
+
+  for (uint32_t from_block : bad) {
+    ASSERT_FALSE(TransformationAddDeadContinue(from_block, false, {})
+                     .IsApplicable(context.get(), fact_manager));
+  }
+  for (uint32_t from_block : good) {
+    const TransformationAddDeadContinue transformation(from_block, false, {});
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %31 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %10 = OpTypePointer Function %6
+         %12 = OpConstant %6 0
+         %20 = OpConstant %6 10
+         %21 = OpTypeBool
+        %100 = OpConstantFalse %21
+         %24 = OpConstant %6 1
+         %38 = OpConstant %6 100
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %43 = OpVariable %10 Function
+         %44 = OpVariable %10 Function
+         %45 = OpVariable %10 Function
+         %31 = OpVariable %10 Function
+               OpStore %31 %12
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %34 %35 None
+               OpBranchConditional %100 %35 %36
+         %36 = OpLabel
+         %37 = OpLoad %6 %31
+         %39 = OpSLessThan %21 %37 %38
+               OpBranchConditional %39 %33 %34
+         %33 = OpLabel
+               OpBranchConditional %100 %35 %35
+         %35 = OpLabel
+               OpStore %43 %12
+               OpStore %44 %12
+               OpBranch %46
+         %46 = OpLabel
+               OpLoopMerge %47 %48 None
+               OpBranchConditional %100 %48 %49
+         %49 = OpLabel
+         %50 = OpLoad %6 %44
+         %51 = OpSLessThan %21 %50 %20
+               OpBranchConditional %51 %52 %47
+         %52 = OpLabel
+         %53 = OpLoad %6 %43
+               OpBranchConditional %100 %48 %101
+        %101 = OpLabel
+         %54 = OpIAdd %6 %53 %24
+               OpStore %43 %54
+               OpBranchConditional %100 %48 %48
+         %48 = OpLabel
+         %55 = OpLoad %6 %44
+         %56 = OpIAdd %6 %55 %24
+               OpStore %44 %56
+               OpBranch %46
+         %47 = OpLabel
+         %57 = OpLoad %6 %43
+               OpStore %45 %57
+         %40 = OpLoad %6 %45
+         %41 = OpLoad %6 %31
+         %42 = OpIAdd %6 %41 %40
+               OpStore %31 %42
+               OpBranch %32
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddDeadContinueTest, PhiInstructions) {
+  // Checks that the transformation works in the presence of phi instructions.
+
+  // The SPIR-V for this test is adapted from the following GLSL, with a bit of
+  // extra and artificial work to get some interesting uses of OpPhi:
+  //
+  // void main() {
+  //   int x; int y;
+  //   float f;
+  //   x = 2;
+  //   f = 3.0;
+  //   if (x > y) {
+  //     x = 3;
+  //     f = 4.0;
+  //   } else {
+  //     x = x + 2;
+  //     f = f + 10.0;
+  //   }
+  //   while (x < y) {
+  //     x = x + 1;
+  //     f = f + 1.0;
+  //   }
+  //   y = x;
+  //   f = f + 3.0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "f"
+               OpName %15 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %10 = OpTypeFloat 32
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 3
+         %17 = OpTypeBool
+         %80 = OpConstantTrue %17
+         %21 = OpConstant %6 3
+         %22 = OpConstant %10 4
+         %27 = OpConstant %10 10
+         %38 = OpConstant %6 1
+         %41 = OpConstant %10 1
+         %46 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %15 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %12 %13
+         %18 = OpSGreaterThan %17 %9 %46
+               OpSelectionMerge %20 None
+               OpBranchConditional %18 %19 %23
+         %19 = OpLabel
+               OpStore %8 %21
+               OpStore %12 %22
+               OpBranch %20
+         %23 = OpLabel
+         %25 = OpIAdd %6 %9 %9
+               OpStore %8 %25
+               OpBranch %70
+         %70 = OpLabel
+         %28 = OpFAdd %10 %13 %27
+               OpStore %12 %28
+               OpBranch %20
+         %20 = OpLabel
+         %52 = OpPhi %10 %22 %19 %28 %70
+         %48 = OpPhi %6 %21 %19 %25 %70
+               OpBranch %29
+         %29 = OpLabel
+         %51 = OpPhi %10 %52 %20 %100 %32
+         %47 = OpPhi %6 %48 %20 %101 %32
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+         %36 = OpSLessThan %17 %47 %46
+               OpBranchConditional %36 %30 %31
+         %30 = OpLabel
+         %39 = OpIAdd %6 %47 %38
+               OpStore %8 %39
+               OpBranch %75
+         %75 = OpLabel
+         %42 = OpFAdd %10 %51 %41
+               OpStore %12 %42
+               OpBranch %32
+         %32 = OpLabel
+        %100 = OpPhi %10 %42 %75
+        %101 = OpPhi %6 %39 %75
+               OpBranch %29
+         %31 = OpLabel
+         %71 = OpPhi %6 %47 %33
+         %72 = OpPhi %10 %51 %33
+               OpStore %15 %71
+         %45 = OpFAdd %10 %72 %13
+               OpStore %12 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  std::vector<uint32_t> bad = {5, 19, 20, 23, 31, 32, 33, 70};
+
+  std::vector<uint32_t> good = {29, 30, 75};
+
+  for (uint32_t from_block : bad) {
+    ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
+                     .IsApplicable(context.get(), fact_manager));
+  }
+  auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21});
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+
+  auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46});
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+
+  // 75 already has the continue block as a successor, so we should not provide
+  // phi ids.
+  auto transformationBad = TransformationAddDeadContinue(75, true, {27, 46});
+  ASSERT_FALSE(transformationBad.IsApplicable(context.get(), fact_manager));
+
+  auto transformation3 = TransformationAddDeadContinue(75, true, {});
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %12 "f"
+               OpName %15 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %10 = OpTypeFloat 32
+         %11 = OpTypePointer Function %10
+         %13 = OpConstant %10 3
+         %17 = OpTypeBool
+         %80 = OpConstantTrue %17
+         %21 = OpConstant %6 3
+         %22 = OpConstant %10 4
+         %27 = OpConstant %10 10
+         %38 = OpConstant %6 1
+         %41 = OpConstant %10 1
+         %46 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %15 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %12 %13
+         %18 = OpSGreaterThan %17 %9 %46
+               OpSelectionMerge %20 None
+               OpBranchConditional %18 %19 %23
+         %19 = OpLabel
+               OpStore %8 %21
+               OpStore %12 %22
+               OpBranch %20
+         %23 = OpLabel
+         %25 = OpIAdd %6 %9 %9
+               OpStore %8 %25
+               OpBranch %70
+         %70 = OpLabel
+         %28 = OpFAdd %10 %13 %27
+               OpStore %12 %28
+               OpBranch %20
+         %20 = OpLabel
+         %52 = OpPhi %10 %22 %19 %28 %70
+         %48 = OpPhi %6 %21 %19 %25 %70
+               OpBranch %29
+         %29 = OpLabel
+         %51 = OpPhi %10 %52 %20 %100 %32
+         %47 = OpPhi %6 %48 %20 %101 %32
+               OpLoopMerge %31 %32 None
+               OpBranchConditional %80 %33 %32
+         %33 = OpLabel
+         %36 = OpSLessThan %17 %47 %46
+               OpBranchConditional %36 %30 %31
+         %30 = OpLabel
+         %39 = OpIAdd %6 %47 %38
+               OpStore %8 %39
+               OpBranchConditional %80 %75 %32
+         %75 = OpLabel
+         %42 = OpFAdd %10 %51 %41
+               OpStore %12 %42
+               OpBranchConditional %80 %32 %32
+         %32 = OpLabel
+        %100 = OpPhi %10 %42 %75 %13 %29 %22 %30
+        %101 = OpPhi %6 %39 %75 %21 %29 %46 %30
+               OpBranch %29
+         %31 = OpLabel
+         %71 = OpPhi %6 %47 %33
+         %72 = OpPhi %10 %51 %33
+               OpStore %15 %71
+         %45 = OpFAdd %10 %72 %13
+               OpStore %12 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_boolean_test.cpp b/test/fuzz/transformation_add_type_boolean_test.cpp
new file mode 100644
index 0000000..9975953
--- /dev/null
+++ b/test/fuzz/transformation_add_type_boolean_test.cpp
@@ -0,0 +1,80 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_boolean.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddTypeBooleanTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Not applicable because id 1 is already in use.
+  ASSERT_FALSE(TransformationAddTypeBoolean(1).IsApplicable(context.get(),
+                                                            fact_manager));
+
+  auto add_type_bool = TransformationAddTypeBoolean(100);
+  ASSERT_TRUE(add_type_bool.IsApplicable(context.get(), fact_manager));
+  add_type_bool.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Not applicable as we already have this type now.
+  ASSERT_FALSE(TransformationAddTypeBoolean(101).IsApplicable(context.get(),
+                                                              fact_manager));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_float_test.cpp b/test/fuzz/transformation_add_type_float_test.cpp
new file mode 100644
index 0000000..67408da
--- /dev/null
+++ b/test/fuzz/transformation_add_type_float_test.cpp
@@ -0,0 +1,80 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_float.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddTypeFloatTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Not applicable because id 1 is already in use.
+  ASSERT_FALSE(TransformationAddTypeFloat(1, 32).IsApplicable(context.get(),
+                                                              fact_manager));
+
+  auto add_type_float_32 = TransformationAddTypeFloat(100, 32);
+  ASSERT_TRUE(add_type_float_32.IsApplicable(context.get(), fact_manager));
+  add_type_float_32.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Not applicable as we already have this type now.
+  ASSERT_FALSE(TransformationAddTypeFloat(101, 32).IsApplicable(context.get(),
+                                                                fact_manager));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeFloat 32
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp
new file mode 100644
index 0000000..c6f884c
--- /dev/null
+++ b/test/fuzz/transformation_add_type_int_test.cpp
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_int.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddTypeIntTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Not applicable because id 1 is already in use.
+  ASSERT_FALSE(TransformationAddTypeInt(1, 32, false)
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto add_type_signed_int_32 = TransformationAddTypeInt(100, 32, true);
+  auto add_type_unsigned_int_32 = TransformationAddTypeInt(101, 32, false);
+  auto add_type_signed_int_32_again = TransformationAddTypeInt(102, 32, true);
+  auto add_type_unsigned_int_32_again =
+      TransformationAddTypeInt(103, 32, false);
+
+  ASSERT_TRUE(add_type_signed_int_32.IsApplicable(context.get(), fact_manager));
+  add_type_signed_int_32.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(
+      add_type_unsigned_int_32.IsApplicable(context.get(), fact_manager));
+  add_type_unsigned_int_32.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Not applicable as we already have these types now.
+  ASSERT_FALSE(
+      add_type_signed_int_32_again.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      add_type_unsigned_int_32_again.IsApplicable(context.get(), fact_manager));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeInt 32 1
+        %101 = OpTypeInt 32 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp
new file mode 100644
index 0000000..e36707f
--- /dev/null
+++ b/test/fuzz/transformation_add_type_pointer_test.cpp
@@ -0,0 +1,206 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_pointer.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddTypePointerTest, BasicTest) {
+  // The SPIR-V was obtained from this GLSL:
+  //
+  // #version 450
+  //
+  // int x;
+  // float y;
+  // vec2 z;
+  //
+  // struct T {
+  //   int a, b;
+  // };
+  //
+  // struct S {
+  //   T t;
+  //   int u;
+  // };
+  //
+  // void main() {
+  //   S myS = S(T(1, 2), 3);
+  //   myS.u = x;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %7 "T"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %8 "S"
+               OpMemberName %8 0 "t"
+               OpMemberName %8 1 "u"
+               OpName %10 "myS"
+               OpName %17 "x"
+               OpName %23 "y"
+               OpName %26 "z"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 1
+         %12 = OpConstant %6 2
+         %13 = OpConstantComposite %7 %11 %12
+         %14 = OpConstant %6 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpTypePointer Private %6
+         %17 = OpVariable %16 Private
+         %19 = OpTypePointer Function %6
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Private %21
+         %23 = OpVariable %22 Private
+         %24 = OpTypeVector %21 2
+         %25 = OpTypePointer Private %24
+         %26 = OpVariable %25 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %15
+         %18 = OpLoad %6 %17
+         %20 = OpAccessChain %19 %10 %11
+               OpStore %20 %18
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto bad_type_id_does_not_exist =
+      TransformationAddTypePointer(100, SpvStorageClassFunction, 101);
+  auto bad_type_id_is_not_type =
+      TransformationAddTypePointer(100, SpvStorageClassFunction, 23);
+  auto bad_result_id_is_not_fresh =
+      TransformationAddTypePointer(17, SpvStorageClassFunction, 21);
+
+  auto good_new_private_pointer_to_t =
+      TransformationAddTypePointer(101, SpvStorageClassPrivate, 7);
+  auto good_new_uniform_pointer_to_t =
+      TransformationAddTypePointer(102, SpvStorageClassUniform, 7);
+  auto good_another_function_pointer_to_s =
+      TransformationAddTypePointer(103, SpvStorageClassFunction, 8);
+  auto good_new_uniform_pointer_to_s =
+      TransformationAddTypePointer(104, SpvStorageClassUniform, 8);
+  auto good_another_private_pointer_to_float =
+      TransformationAddTypePointer(105, SpvStorageClassPrivate, 21);
+  auto good_new_private_pointer_to_private_pointer_to_float =
+      TransformationAddTypePointer(106, SpvStorageClassPrivate, 105);
+  auto good_new_uniform_pointer_to_vec2 =
+      TransformationAddTypePointer(107, SpvStorageClassUniform, 24);
+  auto good_new_private_pointer_to_uniform_pointer_to_vec2 =
+      TransformationAddTypePointer(108, SpvStorageClassPrivate, 107);
+
+  ASSERT_FALSE(
+      bad_type_id_does_not_exist.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_type_id_is_not_type.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      bad_result_id_is_not_fresh.IsApplicable(context.get(), fact_manager));
+
+  for (auto& transformation :
+       {good_new_private_pointer_to_t, good_new_uniform_pointer_to_t,
+        good_another_function_pointer_to_s, good_new_uniform_pointer_to_s,
+        good_another_private_pointer_to_float,
+        good_new_private_pointer_to_private_pointer_to_float,
+        good_new_uniform_pointer_to_vec2,
+        good_new_private_pointer_to_uniform_pointer_to_vec2}) {
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %7 "T"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %8 "S"
+               OpMemberName %8 0 "t"
+               OpMemberName %8 1 "u"
+               OpName %10 "myS"
+               OpName %17 "x"
+               OpName %23 "y"
+               OpName %26 "z"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 1
+         %12 = OpConstant %6 2
+         %13 = OpConstantComposite %7 %11 %12
+         %14 = OpConstant %6 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpTypePointer Private %6
+         %17 = OpVariable %16 Private
+         %19 = OpTypePointer Function %6
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Private %21
+         %23 = OpVariable %22 Private
+         %24 = OpTypeVector %21 2
+         %25 = OpTypePointer Private %24
+         %26 = OpVariable %25 Private
+        %101 = OpTypePointer Private %7
+        %102 = OpTypePointer Uniform %7
+        %103 = OpTypePointer Function %8
+        %104 = OpTypePointer Uniform %8
+        %105 = OpTypePointer Private %21
+        %106 = OpTypePointer Private %105
+        %107 = OpTypePointer Uniform %24
+        %108 = OpTypePointer Private %107
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %15
+         %18 = OpLoad %6 %17
+         %20 = OpAccessChain %19 %10 %11
+               OpStore %20 %18
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_move_block_down_test.cpp b/test/fuzz/transformation_move_block_down_test.cpp
new file mode 100644
index 0000000..02761a2
--- /dev/null
+++ b/test/fuzz/transformation_move_block_down_test.cpp
@@ -0,0 +1,670 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_move_block_down.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible1) {
+  // Block 11 cannot be moved down as it dominates block 12.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = TransformationMoveBlockDown(11);
+  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible2) {
+  // Block 5 cannot be moved down as it is the entry block.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpReturn
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = TransformationMoveBlockDown(5);
+  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible3) {
+  // Block 100 does not exist, so cannot be moved down.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = TransformationMoveBlockDown(100);
+  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible4) {
+  // Block 12 is the last block in its function, so cannot be moved down.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+         %13 = OpFunction %2 None %3
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = TransformationMoveBlockDown(12);
+  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, ManyMovesPossible) {
+  // The SPIR-V arising from this shader has lots of opportunities for moving
+  // blocks around.
+  //
+  // void main() {
+  //   int x;
+  //   int y;
+  //   if (x < y) {
+  //     x = 1;
+  //     if (y == x) {
+  //       x = 3;
+  //     } else {
+  //       x = 4;
+  //     }
+  //   } else {
+  //     if (y < x) {
+  //       x = 5;
+  //     } else {
+  //       x = 6;
+  //     }
+  //   }
+  // }
+
+  std::string before_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %15
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, before_transformation, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // The block ids are: 5 14 20 23 21 25 29 32 30 15
+  // We make a transformation to move each of them down, plus a transformation
+  // to move a non-block, 27, down.
+  auto move_down_5 = TransformationMoveBlockDown(5);
+  auto move_down_14 = TransformationMoveBlockDown(14);
+  auto move_down_20 = TransformationMoveBlockDown(20);
+  auto move_down_23 = TransformationMoveBlockDown(23);
+  auto move_down_21 = TransformationMoveBlockDown(21);
+  auto move_down_25 = TransformationMoveBlockDown(25);
+  auto move_down_29 = TransformationMoveBlockDown(29);
+  auto move_down_32 = TransformationMoveBlockDown(32);
+  auto move_down_30 = TransformationMoveBlockDown(30);
+  auto move_down_15 = TransformationMoveBlockDown(15);
+  auto move_down_27 = TransformationMoveBlockDown(27);
+
+  // Dominance is as follows:
+  //  5 dominates everything else
+  // 14 dominates 20, 23, 21
+  // 20 dominates nothing
+  // 23 dominates nothing
+  // 21 dominates nothing
+  // 25 dominates 29, 32, 30
+  // 29 dominates nothing
+  // 32 dominates nothing
+  // 30 dominates nothing
+  // 15 dominates nothing
+
+  // Current ordering: 5 14 20 23 21 25 29 32 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  // Let's bubble 20 all the way down.
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 20 21 25 29 32 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 20 25 29 32 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 20 29 32 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 20 32 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 20 30 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 30 20 15
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_20.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_15.IsApplicable(context.get(), fact_manager));
+
+  move_down_20.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_bubbling_20_down = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %15
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_bubbling_20_down, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 30 15 20
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+
+  move_down_23.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 21 23 25 29 32 30 15 20
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+
+  move_down_23.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 21 25 23 29 32 30 15 20
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+
+  move_down_21.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 25 21 23 29 32 30 15 20
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+
+  move_down_14.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_more_shuffling = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %21 = OpLabel
+               OpBranch %15
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_more_shuffling, context.get()));
+
+  // Final ordering: 5 25 14 21 23 29 32 30 15 20
+  ASSERT_FALSE(move_down_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_25.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_14.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_21.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_23.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_29.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_32.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_30.IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(move_down_15.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(move_down_20.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, DoNotMoveUnreachable) {
+  // Block 6 is unreachable, so cannot be moved down.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+          %6 = OpLabel
+          %7 = OpUndef %10
+               OpBranch %8
+          %8 = OpLabel
+          %9 = OpCopyObject %10 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto transformation = TransformationMoveBlockDown(6);
+  ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp
new file mode 100644
index 0000000..b97d00f
--- /dev/null
+++ b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp
@@ -0,0 +1,602 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
+     BasicReplacements) {
+  // The test came from the following pseudo-GLSL, where int64 and uint64 denote
+  // 64-bit integer types (they were replaced with int and uint during
+  // translation to SPIR-V, and the generated SPIR-V has been doctored to
+  // accommodate them).
+  //
+  // #version 450
+  //
+  // void main() {
+  //   double d1, d2;
+  //   d1 = 1.0;
+  //   d2 = 2.0;
+  //   float f1, f2;
+  //   f1 = 4.0;
+  //   f2 = 8.0;
+  //   int i1, i2;
+  //   i1 = 100;
+  //   i2 = 200;
+  //
+  //   uint u1, u2;
+  //   u1 = 300u;
+  //   u2 = 400u;
+  //
+  //   int64 i64_1, i64_2;
+  //   i64_1 = 500;
+  //   i64_2 = 600;
+  //
+  //   uint64 u64_1, u64_2;
+  //   u64_1 = 700u;
+  //   u64_2 = 800u;
+  //
+  //   bool b, c, d, e;
+  //   b = true;
+  //   c = false;
+  //   d = true || c;
+  //   c = c && false;
+  // }
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Float64
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "d1"
+               OpName %10 "d2"
+               OpName %14 "f1"
+               OpName %16 "f2"
+               OpName %20 "i1"
+               OpName %22 "i2"
+               OpName %26 "u1"
+               OpName %28 "u2"
+               OpName %30 "i64_1"
+               OpName %32 "i64_2"
+               OpName %34 "u64_1"
+               OpName %36 "u64_2"
+               OpName %40 "b"
+               OpName %42 "c"
+               OpName %44 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 64
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 4
+         %17 = OpConstant %12 8
+         %18 = OpTypeInt 32 1
+         %60 = OpTypeInt 64 1
+         %61 = OpTypePointer Function %60
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 -100
+         %23 = OpConstant %18 200
+         %24 = OpTypeInt 32 0
+         %62 = OpTypeInt 64 0
+         %63 = OpTypePointer Function %62
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 300
+         %29 = OpConstant %24 400
+         %31 = OpConstant %60 -600
+         %33 = OpConstant %60 -500
+         %35 = OpConstant %62 700
+         %37 = OpConstant %62 800
+         %38 = OpTypeBool
+         %39 = OpTypePointer Function %38
+         %41 = OpConstantTrue %38
+         %43 = OpConstantFalse %38
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %16 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %28 = OpVariable %25 Function
+         %30 = OpVariable %61 Function
+         %32 = OpVariable %61 Function
+         %34 = OpVariable %63 Function
+         %36 = OpVariable %63 Function
+         %40 = OpVariable %39 Function
+         %42 = OpVariable %39 Function
+         %44 = OpVariable %39 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %14 %15
+               OpStore %16 %17
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %26 %27
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpStore %34 %35
+               OpStore %36 %37
+               OpStore %40 %41
+               OpStore %42 %43
+         %45 = OpLoad %38 %42
+         %46 = OpLogicalOr %38 %41 %45
+               OpStore %44 %46
+         %47 = OpLoad %38 %42
+         %48 = OpLogicalAnd %38 %47 %43
+               OpStore %42 %48
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  std::vector<protobufs::IdUseDescriptor> uses_of_true = {
+      transformation::MakeIdUseDescriptor(41, SpvOpStore, 1, 44, 12),
+      transformation::MakeIdUseDescriptor(41, SpvOpLogicalOr, 0, 46, 0)};
+
+  std::vector<protobufs::IdUseDescriptor> uses_of_false = {
+      transformation::MakeIdUseDescriptor(43, SpvOpStore, 1, 44, 13),
+      transformation::MakeIdUseDescriptor(43, SpvOpLogicalAnd, 1, 48, 0)};
+
+  const uint32_t fresh_id = 100;
+
+  std::vector<SpvOp> fp_gt_opcodes = {
+      SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
+      SpvOpFUnordGreaterThanEqual};
+
+  std::vector<SpvOp> fp_lt_opcodes = {SpvOpFOrdLessThan, SpvOpFOrdLessThanEqual,
+                                      SpvOpFUnordLessThan,
+                                      SpvOpFUnordLessThanEqual};
+
+  std::vector<SpvOp> int_gt_opcodes = {SpvOpSGreaterThan,
+                                       SpvOpSGreaterThanEqual};
+
+  std::vector<SpvOp> int_lt_opcodes = {SpvOpSLessThan, SpvOpSLessThanEqual};
+
+  std::vector<SpvOp> uint_gt_opcodes = {SpvOpUGreaterThan,
+                                        SpvOpUGreaterThanEqual};
+
+  std::vector<SpvOp> uint_lt_opcodes = {SpvOpULessThan, SpvOpULessThanEqual};
+
+#define CHECK_OPERATOR(USE_DESCRIPTOR, LHS_ID, RHS_ID, OPCODE, FRESH_ID) \
+  ASSERT_TRUE(TransformationReplaceBooleanConstantWithConstantBinary(    \
+                  USE_DESCRIPTOR, LHS_ID, RHS_ID, OPCODE, FRESH_ID)      \
+                  .IsApplicable(context.get(), fact_manager));           \
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(   \
+                   USE_DESCRIPTOR, RHS_ID, LHS_ID, OPCODE, FRESH_ID)     \
+                   .IsApplicable(context.get(), fact_manager));
+
+#define CHECK_TRANSFORMATION_APPLICABILITY(GT_OPCODES, LT_OPCODES, SMALL_ID, \
+                                           LARGE_ID)                         \
+  for (auto gt_opcode : GT_OPCODES) {                                        \
+    for (auto& true_use : uses_of_true) {                                    \
+      CHECK_OPERATOR(true_use, LARGE_ID, SMALL_ID, gt_opcode, fresh_id);     \
+    }                                                                        \
+    for (auto& false_use : uses_of_false) {                                  \
+      CHECK_OPERATOR(false_use, SMALL_ID, LARGE_ID, gt_opcode, fresh_id);    \
+    }                                                                        \
+  }                                                                          \
+  for (auto lt_opcode : LT_OPCODES) {                                        \
+    for (auto& true_use : uses_of_true) {                                    \
+      CHECK_OPERATOR(true_use, SMALL_ID, LARGE_ID, lt_opcode, fresh_id);     \
+    }                                                                        \
+    for (auto& false_use : uses_of_false) {                                  \
+      CHECK_OPERATOR(false_use, LARGE_ID, SMALL_ID, lt_opcode, fresh_id);    \
+    }                                                                        \
+  }
+
+  // Float
+  { CHECK_TRANSFORMATION_APPLICABILITY(fp_gt_opcodes, fp_lt_opcodes, 15, 17); }
+
+  // Double
+  { CHECK_TRANSFORMATION_APPLICABILITY(fp_gt_opcodes, fp_lt_opcodes, 9, 11); }
+
+  // Int32
+  {
+    CHECK_TRANSFORMATION_APPLICABILITY(int_gt_opcodes, int_lt_opcodes, 21, 23);
+  }
+
+  // Int64
+  {
+    CHECK_TRANSFORMATION_APPLICABILITY(int_gt_opcodes, int_lt_opcodes, 31, 33);
+  }
+
+  // Uint32
+  {
+    CHECK_TRANSFORMATION_APPLICABILITY(uint_gt_opcodes, uint_lt_opcodes, 27,
+                                       29);
+  }
+
+  // Uint64
+  {
+    CHECK_TRANSFORMATION_APPLICABILITY(uint_gt_opcodes, uint_lt_opcodes, 35,
+                                       37);
+  }
+
+  // Target id is not fresh
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   uses_of_true[0], 15, 17, SpvOpFOrdLessThan, 15)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // LHS id does not exist
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   uses_of_true[0], 300, 17, SpvOpFOrdLessThan, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // RHS id does not exist
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   uses_of_true[0], 15, 300, SpvOpFOrdLessThan, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // LHS and RHS ids do not match type
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   uses_of_true[0], 11, 17, SpvOpFOrdLessThan, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Opcode not appropriate
+  ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                   uses_of_true[0], 15, 17, SpvOpFDiv, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto replace_true_with_double_comparison =
+      TransformationReplaceBooleanConstantWithConstantBinary(
+          uses_of_true[0], 11, 9, SpvOpFUnordGreaterThan, 100);
+  auto replace_true_with_uint32_comparison =
+      TransformationReplaceBooleanConstantWithConstantBinary(
+          uses_of_true[1], 27, 29, SpvOpULessThanEqual, 101);
+  auto replace_false_with_float_comparison =
+      TransformationReplaceBooleanConstantWithConstantBinary(
+          uses_of_false[0], 17, 15, SpvOpFOrdLessThan, 102);
+  auto replace_false_with_sint64_comparison =
+      TransformationReplaceBooleanConstantWithConstantBinary(
+          uses_of_false[1], 33, 31, SpvOpSLessThan, 103);
+
+  ASSERT_TRUE(replace_true_with_double_comparison.IsApplicable(context.get(),
+                                                               fact_manager));
+  replace_true_with_double_comparison.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(replace_true_with_uint32_comparison.IsApplicable(context.get(),
+                                                               fact_manager));
+  replace_true_with_uint32_comparison.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(replace_false_with_float_comparison.IsApplicable(context.get(),
+                                                               fact_manager));
+  replace_false_with_float_comparison.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(replace_false_with_sint64_comparison.IsApplicable(context.get(),
+                                                                fact_manager));
+  replace_false_with_sint64_comparison.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+               OpCapability Float64
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "d1"
+               OpName %10 "d2"
+               OpName %14 "f1"
+               OpName %16 "f2"
+               OpName %20 "i1"
+               OpName %22 "i2"
+               OpName %26 "u1"
+               OpName %28 "u2"
+               OpName %30 "i64_1"
+               OpName %32 "i64_2"
+               OpName %34 "u64_1"
+               OpName %36 "u64_2"
+               OpName %40 "b"
+               OpName %42 "c"
+               OpName %44 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 64
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 4
+         %17 = OpConstant %12 8
+         %18 = OpTypeInt 32 1
+         %60 = OpTypeInt 64 1
+         %61 = OpTypePointer Function %60
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 -100
+         %23 = OpConstant %18 200
+         %24 = OpTypeInt 32 0
+         %62 = OpTypeInt 64 0
+         %63 = OpTypePointer Function %62
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 300
+         %29 = OpConstant %24 400
+         %31 = OpConstant %60 -600
+         %33 = OpConstant %60 -500
+         %35 = OpConstant %62 700
+         %37 = OpConstant %62 800
+         %38 = OpTypeBool
+         %39 = OpTypePointer Function %38
+         %41 = OpConstantTrue %38
+         %43 = OpConstantFalse %38
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %16 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %28 = OpVariable %25 Function
+         %30 = OpVariable %61 Function
+         %32 = OpVariable %61 Function
+         %34 = OpVariable %63 Function
+         %36 = OpVariable %63 Function
+         %40 = OpVariable %39 Function
+         %42 = OpVariable %39 Function
+         %44 = OpVariable %39 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %14 %15
+               OpStore %16 %17
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %26 %27
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpStore %34 %35
+               OpStore %36 %37
+        %100 = OpFUnordGreaterThan %38 %11 %9
+               OpStore %40 %100
+        %102 = OpFOrdLessThan %38 %17 %15
+               OpStore %42 %102
+         %45 = OpLoad %38 %42
+        %101 = OpULessThanEqual %38 %27 %29
+         %46 = OpLogicalOr %38 %101 %45
+               OpStore %44 %46
+         %47 = OpLoad %38 %42
+        %103 = OpSLessThan %38 %33 %31
+         %48 = OpLogicalAnd %38 %47 %103
+               OpStore %42 %48
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+
+  if (std::numeric_limits<double>::has_quiet_NaN) {
+    double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
+    uint32_t words[2];
+    memcpy(words, &quiet_nan_double, sizeof(double));
+    opt::Instruction::OperandList operands = {
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words[0]}},
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words[1]}}};
+    context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+        context.get(), SpvOpConstant, 6, 200, operands));
+    fuzzerutil::UpdateModuleIdBound(context.get(), 200);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    // The transformation is not applicable because %200 is NaN.
+    ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                     uses_of_true[0], 11, 200, SpvOpFOrdLessThan, 300)
+                     .IsApplicable(context.get(), fact_manager));
+  }
+  if (std::numeric_limits<double>::has_infinity) {
+    double positive_infinity_double = std::numeric_limits<double>::infinity();
+    uint32_t words[2];
+    memcpy(words, &positive_infinity_double, sizeof(double));
+    opt::Instruction::OperandList operands = {
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words[0]}},
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words[1]}}};
+    context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+        context.get(), SpvOpConstant, 6, 201, operands));
+    fuzzerutil::UpdateModuleIdBound(context.get(), 201);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    // Even though the double constant %11 is less than the infinity %201, the
+    // transformation is restricted to only apply to finite values.
+    ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                     uses_of_true[0], 11, 201, SpvOpFOrdLessThan, 300)
+                     .IsApplicable(context.get(), fact_manager));
+  }
+  if (std::numeric_limits<float>::has_infinity) {
+    float positive_infinity_float = std::numeric_limits<float>::infinity();
+    float negative_infinity_float = -1 * positive_infinity_float;
+    uint32_t words_positive_infinity[1];
+    uint32_t words_negative_infinity[1];
+    memcpy(words_positive_infinity, &positive_infinity_float, sizeof(float));
+    memcpy(words_negative_infinity, &negative_infinity_float, sizeof(float));
+    opt::Instruction::OperandList operands_positive_infinity = {
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words_positive_infinity[0]}}};
+    context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+        context.get(), SpvOpConstant, 12, 202, operands_positive_infinity));
+    fuzzerutil::UpdateModuleIdBound(context.get(), 202);
+    opt::Instruction::OperandList operands = {
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {words_negative_infinity[0]}}};
+    context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+        context.get(), SpvOpConstant, 12, 203, operands));
+    fuzzerutil::UpdateModuleIdBound(context.get(), 203);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    // Even though the negative infinity at %203 is less than the positive
+    // infinity %202, the transformation is restricted to only apply to finite
+    // values.
+    ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
+                     uses_of_true[0], 203, 202, SpvOpFOrdLessThan, 300)
+                     .IsApplicable(context.get(), fact_manager));
+  }
+}
+
+TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest,
+     MergeInstructions) {
+  // The test came from the following GLSL:
+  //
+  // void main() {
+  //   int x = 1;
+  //   int y = 2;
+  //   if (true) {
+  //     x = 2;
+  //   }
+  //   while(false) {
+  //     y = 2;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %21 = OpConstantFalse %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %15
+         %14 = OpLabel
+               OpStore %8 %11
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %18 %19 None
+               OpBranchConditional %21 %17 %18
+         %17 = OpLabel
+               OpStore %10 %11
+               OpBranch %19
+         %19 = OpLabel
+               OpBranch %16
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto use_of_true_in_if =
+      transformation::MakeIdUseDescriptor(13, SpvOpBranchConditional, 0, 10, 0);
+  auto use_of_false_in_while =
+      transformation::MakeIdUseDescriptor(21, SpvOpBranchConditional, 0, 16, 0);
+
+  auto replacement_1 = TransformationReplaceBooleanConstantWithConstantBinary(
+      use_of_true_in_if, 9, 11, SpvOpSLessThan, 100);
+  auto replacement_2 = TransformationReplaceBooleanConstantWithConstantBinary(
+      use_of_false_in_while, 9, 11, SpvOpSGreaterThanEqual, 101);
+
+  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
+  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
+  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %21 = OpConstantFalse %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+        %100 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %100 %14 %15
+         %14 = OpLabel
+               OpStore %8 %11
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+        %101 = OpSGreaterThanEqual %12 %9 %11
+               OpLoopMerge %18 %19 None
+               OpBranchConditional %101 %17 %18
+         %17 = OpLabel
+               OpStore %10 %11
+               OpBranch %19
+         %19 = OpLabel
+               OpBranch %16
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
new file mode 100644
index 0000000..06ee025
--- /dev/null
+++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
@@ -0,0 +1,1446 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+bool AddFactHelper(
+    FactManager* fact_manager, opt::IRContext* context, uint32_t word,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  constant_uniform_fact.add_constant_word(word);
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->AddFact(fact, context);
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int a;
+  //   int b;
+  //   int c;
+  // };
+  //
+  // void main()
+  // {
+  //   int x;
+  //   x = 1;
+  //   x = x + 2;
+  //   x = 3 + x;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpIAdd %6 %10 %11
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_a =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+  protobufs::UniformBufferElementDescriptor blockname_b =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+  protobufs::UniformBufferElementDescriptor blockname_c =
+      MakeUniformBufferElementDescriptor(0, 0, {2});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_a));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_b));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_c));
+
+  // The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+  protobufs::IdUseDescriptor use_of_11_in_add =
+      transformation::MakeIdUseDescriptor(11, SpvOpIAdd, 1, 12, 0);
+  protobufs::IdUseDescriptor use_of_14_in_add =
+      transformation::MakeIdUseDescriptor(14, SpvOpIAdd, 0, 15, 0);
+
+  // These transformations work: they match the facts.
+  auto transformation_use_of_9_in_store =
+      TransformationReplaceConstantWithUniform(use_of_9_in_store, blockname_a,
+                                               100, 101);
+  ASSERT_TRUE(transformation_use_of_9_in_store.IsApplicable(context.get(),
+                                                            fact_manager));
+  auto transformation_use_of_11_in_add =
+      TransformationReplaceConstantWithUniform(use_of_11_in_add, blockname_b,
+                                               102, 103);
+  ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  auto transformation_use_of_14_in_add =
+      TransformationReplaceConstantWithUniform(use_of_14_in_add, blockname_c,
+                                               104, 105);
+  ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+
+  // The transformations are not applicable if we change which uniforms are
+  // applied to which constants.
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                        blockname_b, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
+                                                        blockname_c, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_14_in_add,
+                                                        blockname_a, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // The following transformations do not apply because the uniform descriptors
+  // are not sensible.
+  protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor1 =
+      MakeUniformBufferElementDescriptor(1, 2, {0});
+  protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor2 =
+      MakeUniformBufferElementDescriptor(0, 0, {5});
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(
+                   use_of_9_in_store, nonsense_uniform_descriptor1, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(
+                   use_of_9_in_store, nonsense_uniform_descriptor2, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // The following transformation does not apply because the id descriptor is
+  // not sensible.
+  protobufs::IdUseDescriptor nonsense_id_use_descriptor =
+      transformation::MakeIdUseDescriptor(9, SpvOpIAdd, 0, 15, 0);
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(
+                   nonsense_id_use_descriptor, blockname_a, 101, 102)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // The following transformations do not apply because the ids are not fresh.
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
+                                                        blockname_b, 15, 103)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_add,
+                                                        blockname_b, 102, 15)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Apply the use of 9 in a store.
+  transformation_use_of_9_in_store.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_9_in_store = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+         %12 = OpIAdd %6 %10 %11
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_9_in_store, context.get()));
+
+  ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  // Apply the use of 11 in an add.
+  transformation_use_of_11_in_add.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_11_in_add = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+        %102 = OpAccessChain %51 %18 %9
+        %103 = OpLoad %6 %102
+         %12 = OpIAdd %6 %10 %103
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_11_in_add, context.get()));
+
+  ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  // Apply the use of 15 in an add.
+  transformation_use_of_14_in_add.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_14_in_add = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+        %102 = OpAccessChain %51 %18 %9
+        %103 = OpLoad %6 %102
+         %12 = OpIAdd %6 %10 %103
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+        %104 = OpAccessChain %51 %18 %11
+        %105 = OpLoad %6 %104
+         %15 = OpIAdd %6 %105 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_14_in_add, context.get()));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NestedStruct) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // struct U {
+  //   int x; // == 4
+  // };
+  //
+  // struct T {
+  //   int x; // == 3
+  //   U y;
+  // };
+  //
+  // struct S {
+  //   T x;
+  //   int y; // == 2
+  // };
+  //
+  // uniform blockname {
+  //   int x; // == 1
+  //   S y;
+  // };
+  //
+  // void foo(int a) { }
+  //
+  // void main()
+  // {
+  //   int x;
+  //   x = 1;
+  //   x = x + 2;
+  //   x = 3 + x;
+  //   foo(4);
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %10 "foo(i1;"
+               OpName %9 "a"
+               OpName %12 "x"
+               OpName %21 "param"
+               OpName %23 "U"
+               OpMemberName %23 0 "x"
+               OpName %24 "T"
+               OpMemberName %24 0 "x"
+               OpMemberName %24 1 "y"
+               OpName %25 "S"
+               OpMemberName %25 0 "x"
+               OpMemberName %25 1 "y"
+               OpName %26 "blockname"
+               OpMemberName %26 0 "x"
+               OpMemberName %26 1 "y"
+               OpName %28 ""
+               OpMemberDecorate %23 0 Offset 0
+               OpMemberDecorate %24 0 Offset 0
+               OpMemberDecorate %24 1 Offset 16
+               OpMemberDecorate %25 0 Offset 0
+               OpMemberDecorate %25 1 Offset 32
+               OpMemberDecorate %26 0 Offset 0
+               OpMemberDecorate %26 1 Offset 16
+               OpDecorate %26 Block
+               OpDecorate %28 DescriptorSet 0
+               OpDecorate %28 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpConstant %6 0
+         %13 = OpConstant %6 1
+         %15 = OpConstant %6 2
+         %17 = OpConstant %6 3
+         %20 = OpConstant %6 4
+         %23 = OpTypeStruct %6
+         %24 = OpTypeStruct %6 %23
+         %25 = OpTypeStruct %24 %6
+         %26 = OpTypeStruct %6 %25
+         %27 = OpTypePointer Uniform %26
+         %51 = OpTypePointer Uniform %6
+         %28 = OpVariable %27 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpStore %12 %16
+         %18 = OpLoad %6 %12
+         %19 = OpIAdd %6 %17 %18
+               OpStore %12 %19
+               OpStore %21 %20
+         %22 = OpFunctionCall %2 %10 %21
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_1 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+  protobufs::UniformBufferElementDescriptor blockname_2 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 1});
+  protobufs::UniformBufferElementDescriptor blockname_3 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 0, 0});
+  protobufs::UniformBufferElementDescriptor blockname_4 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 0, 1, 0});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_1));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_2));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_3));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, blockname_4));
+
+  // The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively.
+  protobufs::IdUseDescriptor use_of_13_in_store =
+      transformation::MakeIdUseDescriptor(13, SpvOpStore, 1, 21, 0);
+  protobufs::IdUseDescriptor use_of_15_in_add =
+      transformation::MakeIdUseDescriptor(15, SpvOpIAdd, 1, 16, 0);
+  protobufs::IdUseDescriptor use_of_17_in_add =
+      transformation::MakeIdUseDescriptor(17, SpvOpIAdd, 0, 19, 0);
+  protobufs::IdUseDescriptor use_of_20_in_store =
+      transformation::MakeIdUseDescriptor(20, SpvOpStore, 1, 19, 1);
+
+  // These transformations work: they match the facts.
+  auto transformation_use_of_13_in_store =
+      TransformationReplaceConstantWithUniform(use_of_13_in_store, blockname_1,
+                                               100, 101);
+  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+  auto transformation_use_of_15_in_add =
+      TransformationReplaceConstantWithUniform(use_of_15_in_add, blockname_2,
+                                               102, 103);
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  auto transformation_use_of_17_in_add =
+      TransformationReplaceConstantWithUniform(use_of_17_in_add, blockname_3,
+                                               104, 105);
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  auto transformation_use_of_20_in_store =
+      TransformationReplaceConstantWithUniform(use_of_20_in_store, blockname_4,
+                                               106, 107);
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  ASSERT_TRUE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  transformation_use_of_13_in_store.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                              fact_manager));
+  ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  transformation_use_of_15_in_add.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                              fact_manager));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                            fact_manager));
+  ASSERT_TRUE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                           fact_manager));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  transformation_use_of_17_in_add.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                              fact_manager));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                            fact_manager));
+  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                            fact_manager));
+  ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                             fact_manager));
+
+  transformation_use_of_20_in_store.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(context.get(),
+                                                              fact_manager));
+  ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(context.get(),
+                                                            fact_manager));
+  ASSERT_FALSE(transformation_use_of_17_in_add.IsApplicable(context.get(),
+                                                            fact_manager));
+  ASSERT_FALSE(transformation_use_of_20_in_store.IsApplicable(context.get(),
+                                                              fact_manager));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %10 "foo(i1;"
+               OpName %9 "a"
+               OpName %12 "x"
+               OpName %21 "param"
+               OpName %23 "U"
+               OpMemberName %23 0 "x"
+               OpName %24 "T"
+               OpMemberName %24 0 "x"
+               OpMemberName %24 1 "y"
+               OpName %25 "S"
+               OpMemberName %25 0 "x"
+               OpMemberName %25 1 "y"
+               OpName %26 "blockname"
+               OpMemberName %26 0 "x"
+               OpMemberName %26 1 "y"
+               OpName %28 ""
+               OpMemberDecorate %23 0 Offset 0
+               OpMemberDecorate %24 0 Offset 0
+               OpMemberDecorate %24 1 Offset 16
+               OpMemberDecorate %25 0 Offset 0
+               OpMemberDecorate %25 1 Offset 32
+               OpMemberDecorate %26 0 Offset 0
+               OpMemberDecorate %26 1 Offset 16
+               OpDecorate %26 Block
+               OpDecorate %28 DescriptorSet 0
+               OpDecorate %28 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpConstant %6 0
+         %13 = OpConstant %6 1
+         %15 = OpConstant %6 2
+         %17 = OpConstant %6 3
+         %20 = OpConstant %6 4
+         %23 = OpTypeStruct %6
+         %24 = OpTypeStruct %6 %23
+         %25 = OpTypeStruct %24 %6
+         %26 = OpTypeStruct %6 %25
+         %27 = OpTypePointer Uniform %26
+         %51 = OpTypePointer Uniform %6
+         %28 = OpVariable %27 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %28 %50
+        %101 = OpLoad %6 %100
+               OpStore %12 %101
+         %14 = OpLoad %6 %12
+        %102 = OpAccessChain %51 %28 %13 %13
+        %103 = OpLoad %6 %102
+         %16 = OpIAdd %6 %14 %103
+               OpStore %12 %16
+         %18 = OpLoad %6 %12
+        %104 = OpAccessChain %51 %28 %13 %50 %50
+        %105 = OpLoad %6 %104
+         %19 = OpIAdd %6 %105 %18
+               OpStore %12 %19
+        %106 = OpAccessChain %51 %28 %13 %50 %13 %50
+        %107 = OpLoad %6 %106
+               OpStore %21 %107
+         %22 = OpFunctionCall %2 %10 %21
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NoUniformIntPointerPresent) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 0
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   a = 0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "x"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_0 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 0, blockname_0));
+
+  // The constant id is 9 for 0.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no uniform pointer to integer
+  // type is present:
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                        blockname_0, 100, 101)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 0
+  //   int y; // == 9
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   a = 9;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "x"
+               OpMemberName %10 1 "y"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpMemberDecorate %10 1 Offset 4
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 9
+         %10 = OpTypeStruct %6 %6
+         %11 = OpTypePointer Uniform %10
+         %50 = OpTypePointer Uniform %6
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_0 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+  protobufs::UniformBufferElementDescriptor blockname_9 =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
+
+  // The constant id is 9 for 9.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no constant is present for the
+  // index 1 required to index into the uniform buffer:
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                        blockname_9, 100, 101)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest,
+     NoIntTypePresentToEnableIndexing) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   float f; // == 9
+  // };
+  //
+  // void main()
+  // {
+  //   float a;
+  //   a = 3.0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "f"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_3 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  uint32_t float_data[1];
+  float temp = 3.0;
+  memcpy(&float_data[0], &temp, sizeof(float));
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), float_data[0], blockname_3));
+
+  // The constant id is 9 for 3.0.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no integer type is present to
+  // allow a constant index to be expressed:
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                        blockname_3, 100, 101)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest,
+     UniformFactsDoNotMatchConstants) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 9
+  //   int y; // == 10
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   int b;
+  //   a = 9;
+  //   b = 10;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpName %12 "blockname"
+               OpMemberName %12 0 "x"
+               OpMemberName %12 1 "y"
+               OpName %14 ""
+               OpMemberDecorate %12 0 Offset 0
+               OpMemberDecorate %12 1 Offset 4
+               OpDecorate %12 Block
+               OpDecorate %14 DescriptorSet 0
+               OpDecorate %14 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 9
+         %11 = OpConstant %6 10
+         %50 = OpConstant %6 0
+         %51 = OpConstant %6 1
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Uniform %12
+         %52 = OpTypePointer Uniform %6
+         %14 = OpVariable %13 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_9 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+  protobufs::UniformBufferElementDescriptor blockname_10 =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 10, blockname_10));
+
+  // The constant ids for 9 and 10 are 9 and 11 respectively
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 10, 0);
+  protobufs::IdUseDescriptor use_of_11_in_store =
+      transformation::MakeIdUseDescriptor(11, SpvOpStore, 1, 10, 1);
+
+  // These are right:
+  ASSERT_TRUE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                       blockname_9, 100, 101)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationReplaceConstantWithUniform(use_of_11_in_store,
+                                                       blockname_10, 102, 103)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // These are wrong because the constants do not match the facts about
+  // uniforms.
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_11_in_store,
+                                                        blockname_9, 100, 101)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationReplaceConstantWithUniform(use_of_9_in_store,
+                                                        blockname_10, 102, 103)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) {
+  // The following GLSL was the basis for this test:
+
+  // #version 450
+  //
+  // struct T {
+  //   float a[5]; // [1.0, 1.5, 1.75, 1.875, 1.9375]
+  //   ivec4 b; // (1, 2, 3, 4)
+  //   vec3 c; // (2.0, 2.5, 2.75)
+  //   uint d; // 42u
+  //   bool e; // Not used in test
+  // };
+  //
+  // uniform block {
+  //   T f;
+  //   int g; // 22
+  //   uvec2 h; // (100u, 200u)
+  // };
+  //
+  // void main()
+  // {
+  //   T myT;
+  //
+  //   myT.a[0] = 1.9375;
+  //   myT.a[1] = 1.875;
+  //   myT.a[2] = 1.75;
+  //   myT.a[3] = 1.5;
+  //   myT.a[4] = 1.0;
+  //
+  //   myT.b.x = 4;
+  //   myT.b.y = 3;
+  //   myT.b.z = 2;
+  //   myT.b.w = 1;
+  //
+  //   myT.b.r = 22;
+  //
+  //   myT.c[0] = 2.75;
+  //   myT.c[0] = 2.5;
+  //   myT.c[0] = 2.0;
+  //
+  //   myT.d = 42u;
+  //   myT.d = 100u;
+  //   myT.d = 200u;
+  //
+  //   myT.e = true; // No attempt to replace 'true' by a uniform value
+  //
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %14 "T"
+               OpMemberName %14 0 "a"
+               OpMemberName %14 1 "b"
+               OpMemberName %14 2 "c"
+               OpMemberName %14 3 "d"
+               OpMemberName %14 4 "e"
+               OpName %16 "myT"
+               OpName %61 "T"
+               OpMemberName %61 0 "a"
+               OpMemberName %61 1 "b"
+               OpMemberName %61 2 "c"
+               OpMemberName %61 3 "d"
+               OpMemberName %61 4 "e"
+               OpName %63 "block"
+               OpMemberName %63 0 "f"
+               OpMemberName %63 1 "g"
+               OpMemberName %63 2 "h"
+               OpName %65 ""
+               OpDecorate %60 ArrayStride 16
+               OpMemberDecorate %61 0 Offset 0
+               OpMemberDecorate %61 1 Offset 80
+               OpMemberDecorate %61 2 Offset 96
+               OpMemberDecorate %61 3 Offset 108
+               OpMemberDecorate %61 4 Offset 112
+               OpMemberDecorate %63 0 Offset 0
+               OpMemberDecorate %63 1 Offset 128
+               OpMemberDecorate %63 2 Offset 136
+               OpDecorate %63 Block
+               OpDecorate %65 DescriptorSet 0
+               OpDecorate %65 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 5
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeVector %6 3
+         %13 = OpTypeBool
+         %14 = OpTypeStruct %9 %11 %12 %7 %13
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %10 0
+         %18 = OpConstant %6 1.9375
+         %19 = OpTypePointer Function %6
+         %21 = OpConstant %10 1
+         %22 = OpConstant %6 1.875
+         %24 = OpConstant %10 2
+         %25 = OpConstant %6 1.75
+         %27 = OpConstant %10 3
+         %28 = OpConstant %6 1.5
+         %30 = OpConstant %10 4
+         %31 = OpConstant %6 1
+         %33 = OpConstant %7 0
+         %34 = OpTypePointer Function %10
+         %36 = OpConstant %7 1
+         %38 = OpConstant %7 2
+         %40 = OpConstant %7 3
+         %42 = OpConstant %10 22
+         %44 = OpConstant %6 2.75
+         %46 = OpConstant %6 2.5
+         %48 = OpConstant %6 2
+         %50 = OpConstant %7 42
+         %51 = OpTypePointer Function %7
+         %53 = OpConstant %7 100
+         %55 = OpConstant %7 200
+         %57 = OpConstantTrue %13
+         %58 = OpTypePointer Function %13
+         %60 = OpTypeArray %6 %8
+         %61 = OpTypeStruct %60 %11 %12 %7 %7
+         %62 = OpTypeVector %7 2
+         %63 = OpTypeStruct %61 %10 %62
+         %64 = OpTypePointer Uniform %63
+        %100 = OpTypePointer Uniform %10
+        %101 = OpTypePointer Uniform %7
+        %102 = OpTypePointer Uniform %6
+        %103 = OpTypePointer Uniform %13
+         %65 = OpVariable %64 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %16 = OpVariable %15 Function
+         %20 = OpAccessChain %19 %16 %17 %17
+               OpStore %20 %18
+         %23 = OpAccessChain %19 %16 %17 %21
+               OpStore %23 %22
+         %26 = OpAccessChain %19 %16 %17 %24
+               OpStore %26 %25
+         %29 = OpAccessChain %19 %16 %17 %27
+               OpStore %29 %28
+         %32 = OpAccessChain %19 %16 %17 %30
+               OpStore %32 %31
+         %35 = OpAccessChain %34 %16 %21 %33
+               OpStore %35 %30
+         %37 = OpAccessChain %34 %16 %21 %36
+               OpStore %37 %27
+         %39 = OpAccessChain %34 %16 %21 %38
+               OpStore %39 %24
+         %41 = OpAccessChain %34 %16 %21 %40
+               OpStore %41 %21
+         %43 = OpAccessChain %34 %16 %21 %33
+               OpStore %43 %42
+         %45 = OpAccessChain %19 %16 %24 %33
+               OpStore %45 %44
+         %47 = OpAccessChain %19 %16 %24 %33
+               OpStore %47 %46
+         %49 = OpAccessChain %19 %16 %24 %33
+               OpStore %49 %48
+         %52 = OpAccessChain %51 %16 %27
+               OpStore %52 %50
+         %54 = OpAccessChain %51 %16 %27
+               OpStore %54 %53
+         %56 = OpAccessChain %51 %16 %27
+               OpStore %56 %55
+         %59 = OpAccessChain %58 %16 %30
+               OpStore %59 %57
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  const float float_array_values[5] = {1.0, 1.5, 1.75, 1.875, 1.9375};
+  uint32_t float_array_data[5];
+  memcpy(&float_array_data, &float_array_values, sizeof(float_array_values));
+
+  const float float_vector_values[3] = {2.0, 2.5, 2.75};
+  uint32_t float_vector_data[3];
+  memcpy(&float_vector_data, &float_vector_values, sizeof(float_vector_values));
+
+  protobufs::UniformBufferElementDescriptor uniform_f_a_0 =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_1 =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_2 =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 2});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_3 =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 3});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_4 =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 4});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_b_x =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_y =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_z =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 2});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_w =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 3});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_c_x =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_c_y =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_c_z =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 2});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_d =
+      MakeUniformBufferElementDescriptor(0, 0, {0, 3});
+
+  protobufs::UniformBufferElementDescriptor uniform_g =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+
+  protobufs::UniformBufferElementDescriptor uniform_h_x =
+      MakeUniformBufferElementDescriptor(0, 0, {2, 0});
+  protobufs::UniformBufferElementDescriptor uniform_h_y =
+      MakeUniformBufferElementDescriptor(0, 0, {2, 1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[0],
+                            uniform_f_a_0));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[1],
+                            uniform_f_a_1));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[2],
+                            uniform_f_a_2));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[3],
+                            uniform_f_a_3));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[4],
+                            uniform_f_a_4));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, uniform_f_b_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, uniform_f_b_y));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, uniform_f_b_z));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, uniform_f_b_w));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[0],
+                            uniform_f_c_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[1],
+                            uniform_f_c_y));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[2],
+                            uniform_f_c_z));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 42, uniform_f_d));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22, uniform_g));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 100, uniform_h_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200, uniform_h_y));
+
+  std::vector<TransformationReplaceConstantWithUniform> transformations;
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(18, SpvOpStore, 1, 20, 0),
+      uniform_f_a_4, 200, 201));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(22, SpvOpStore, 1, 23, 0),
+      uniform_f_a_3, 202, 203));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(25, SpvOpStore, 1, 26, 0),
+      uniform_f_a_2, 204, 205));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(28, SpvOpStore, 1, 29, 0),
+      uniform_f_a_1, 206, 207));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(31, SpvOpStore, 1, 32, 0),
+      uniform_f_a_0, 208, 209));
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(30, SpvOpStore, 1, 35, 0),
+      uniform_f_b_w, 210, 211));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(27, SpvOpStore, 1, 37, 0),
+      uniform_f_b_z, 212, 213));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(24, SpvOpStore, 1, 39, 0),
+      uniform_f_b_y, 214, 215));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(21, SpvOpStore, 1, 41, 0),
+      uniform_f_b_x, 216, 217));
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(44, SpvOpStore, 1, 45, 0),
+      uniform_f_c_z, 220, 221));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(46, SpvOpStore, 1, 47, 0),
+      uniform_f_c_y, 222, 223));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(48, SpvOpStore, 1, 49, 0),
+      uniform_f_c_x, 224, 225));
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(50, SpvOpStore, 1, 52, 0),
+      uniform_f_d, 226, 227));
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(53, SpvOpStore, 1, 54, 0),
+      uniform_h_x, 228, 229));
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(55, SpvOpStore, 1, 56, 0),
+      uniform_h_y, 230, 231));
+
+  transformations.emplace_back(TransformationReplaceConstantWithUniform(
+      transformation::MakeIdUseDescriptor(42, SpvOpStore, 1, 43, 0), uniform_g,
+      218, 219));
+
+  for (auto& transformation : transformations) {
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %14 "T"
+               OpMemberName %14 0 "a"
+               OpMemberName %14 1 "b"
+               OpMemberName %14 2 "c"
+               OpMemberName %14 3 "d"
+               OpMemberName %14 4 "e"
+               OpName %16 "myT"
+               OpName %61 "T"
+               OpMemberName %61 0 "a"
+               OpMemberName %61 1 "b"
+               OpMemberName %61 2 "c"
+               OpMemberName %61 3 "d"
+               OpMemberName %61 4 "e"
+               OpName %63 "block"
+               OpMemberName %63 0 "f"
+               OpMemberName %63 1 "g"
+               OpMemberName %63 2 "h"
+               OpName %65 ""
+               OpDecorate %60 ArrayStride 16
+               OpMemberDecorate %61 0 Offset 0
+               OpMemberDecorate %61 1 Offset 80
+               OpMemberDecorate %61 2 Offset 96
+               OpMemberDecorate %61 3 Offset 108
+               OpMemberDecorate %61 4 Offset 112
+               OpMemberDecorate %63 0 Offset 0
+               OpMemberDecorate %63 1 Offset 128
+               OpMemberDecorate %63 2 Offset 136
+               OpDecorate %63 Block
+               OpDecorate %65 DescriptorSet 0
+               OpDecorate %65 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 5
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeVector %6 3
+         %13 = OpTypeBool
+         %14 = OpTypeStruct %9 %11 %12 %7 %13
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %10 0
+         %18 = OpConstant %6 1.9375
+         %19 = OpTypePointer Function %6
+         %21 = OpConstant %10 1
+         %22 = OpConstant %6 1.875
+         %24 = OpConstant %10 2
+         %25 = OpConstant %6 1.75
+         %27 = OpConstant %10 3
+         %28 = OpConstant %6 1.5
+         %30 = OpConstant %10 4
+         %31 = OpConstant %6 1
+         %33 = OpConstant %7 0
+         %34 = OpTypePointer Function %10
+         %36 = OpConstant %7 1
+         %38 = OpConstant %7 2
+         %40 = OpConstant %7 3
+         %42 = OpConstant %10 22
+         %44 = OpConstant %6 2.75
+         %46 = OpConstant %6 2.5
+         %48 = OpConstant %6 2
+         %50 = OpConstant %7 42
+         %51 = OpTypePointer Function %7
+         %53 = OpConstant %7 100
+         %55 = OpConstant %7 200
+         %57 = OpConstantTrue %13
+         %58 = OpTypePointer Function %13
+         %60 = OpTypeArray %6 %8
+         %61 = OpTypeStruct %60 %11 %12 %7 %7
+         %62 = OpTypeVector %7 2
+         %63 = OpTypeStruct %61 %10 %62
+         %64 = OpTypePointer Uniform %63
+        %100 = OpTypePointer Uniform %10
+        %101 = OpTypePointer Uniform %7
+        %102 = OpTypePointer Uniform %6
+        %103 = OpTypePointer Uniform %13
+         %65 = OpVariable %64 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %16 = OpVariable %15 Function
+         %20 = OpAccessChain %19 %16 %17 %17
+        %200 = OpAccessChain %102 %65 %17 %17 %30
+        %201 = OpLoad %6 %200
+               OpStore %20 %201
+         %23 = OpAccessChain %19 %16 %17 %21
+        %202 = OpAccessChain %102 %65 %17 %17 %27
+        %203 = OpLoad %6 %202
+               OpStore %23 %203
+         %26 = OpAccessChain %19 %16 %17 %24
+        %204 = OpAccessChain %102 %65 %17 %17 %24
+        %205 = OpLoad %6 %204
+               OpStore %26 %205
+         %29 = OpAccessChain %19 %16 %17 %27
+        %206 = OpAccessChain %102 %65 %17 %17 %21
+        %207 = OpLoad %6 %206
+               OpStore %29 %207
+         %32 = OpAccessChain %19 %16 %17 %30
+        %208 = OpAccessChain %102 %65 %17 %17 %17
+        %209 = OpLoad %6 %208
+               OpStore %32 %209
+         %35 = OpAccessChain %34 %16 %21 %33
+        %210 = OpAccessChain %100 %65 %17 %21 %27
+        %211 = OpLoad %10 %210
+               OpStore %35 %211
+         %37 = OpAccessChain %34 %16 %21 %36
+        %212 = OpAccessChain %100 %65 %17 %21 %24
+        %213 = OpLoad %10 %212
+               OpStore %37 %213
+         %39 = OpAccessChain %34 %16 %21 %38
+        %214 = OpAccessChain %100 %65 %17 %21 %21
+        %215 = OpLoad %10 %214
+               OpStore %39 %215
+         %41 = OpAccessChain %34 %16 %21 %40
+        %216 = OpAccessChain %100 %65 %17 %21 %17
+        %217 = OpLoad %10 %216
+               OpStore %41 %217
+         %43 = OpAccessChain %34 %16 %21 %33
+        %218 = OpAccessChain %100 %65 %21
+        %219 = OpLoad %10 %218
+               OpStore %43 %219
+         %45 = OpAccessChain %19 %16 %24 %33
+        %220 = OpAccessChain %102 %65 %17 %24 %24
+        %221 = OpLoad %6 %220
+               OpStore %45 %221
+         %47 = OpAccessChain %19 %16 %24 %33
+        %222 = OpAccessChain %102 %65 %17 %24 %21
+        %223 = OpLoad %6 %222
+               OpStore %47 %223
+         %49 = OpAccessChain %19 %16 %24 %33
+        %224 = OpAccessChain %102 %65 %17 %24 %17
+        %225 = OpLoad %6 %224
+               OpStore %49 %225
+         %52 = OpAccessChain %51 %16 %27
+        %226 = OpAccessChain %101 %65 %17 %27
+        %227 = OpLoad %7 %226
+               OpStore %52 %227
+         %54 = OpAccessChain %51 %16 %27
+        %228 = OpAccessChain %101 %65 %24 %17
+        %229 = OpLoad %7 %228
+               OpStore %54 %229
+         %56 = OpAccessChain %51 %16 %27
+        %230 = OpAccessChain %101 %65 %24 %21
+        %231 = OpLoad %7 %230
+               OpStore %56 %231
+         %59 = OpAccessChain %58 %16 %30
+               OpStore %59 %57
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp
new file mode 100644
index 0000000..d33ccba
--- /dev/null
+++ b/test/fuzz/transformation_split_block_test.cpp
@@ -0,0 +1,749 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_split_block.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSplitBlockTest, NotApplicable) {
+  // The SPIR-V in this test came from the following fragment shader, with
+  // local store elimination applied to get some OpPhi instructions.
+  //
+  // void main() {
+  //   int x;
+  //   int i;
+  //   for (i = 0; i < 100; i++) {
+  //     x += i;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i"
+               OpName %19 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %24 = OpConstant %6 1
+         %28 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+         %27 = OpPhi %6 %28 %5 %22 %13
+         %26 = OpPhi %6 %9 %5 %25 %13
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %26 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %22 = OpIAdd %6 %27 %26
+               OpStore %19 %22
+               OpBranch %13
+         %13 = OpLabel
+         %25 = OpIAdd %6 %26 %24
+               OpStore %8 %25
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // No split before OpVariable
+  ASSERT_FALSE(TransformationSplitBlock(8, 0, 100).IsApplicable(context.get(),
+                                                                fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(8, 1, 100).IsApplicable(context.get(),
+                                                                fact_manager));
+
+  // No split before OpLabel
+  ASSERT_FALSE(TransformationSplitBlock(14, 0, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // No split if base instruction is outside a function
+  ASSERT_FALSE(TransformationSplitBlock(1, 0, 100).IsApplicable(context.get(),
+                                                                fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(1, 4, 100).IsApplicable(context.get(),
+                                                                fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(1, 35, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // No split if block is loop header
+  ASSERT_FALSE(TransformationSplitBlock(27, 0, 100)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(27, 1, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // No split if base instruction does not exist
+  ASSERT_FALSE(TransformationSplitBlock(88, 0, 100)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(88, 22, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // No split if offset is too large (goes into another block)
+  ASSERT_FALSE(TransformationSplitBlock(18, 3, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // No split if id in use
+  ASSERT_FALSE(TransformationSplitBlock(18, 0, 27).IsApplicable(context.get(),
+                                                                fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(18, 0, 14).IsApplicable(context.get(),
+                                                                fact_manager));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockSeveralTimes) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int a;
+  //   int b;
+  //   a = 1;
+  //   b = a;
+  //   a = b;
+  //   b = 2;
+  //   b++;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto split_1 = TransformationSplitBlock(5, 3, 100);
+  ASSERT_TRUE(split_1.IsApplicable(context.get(), fact_manager));
+  split_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_1, context.get()));
+
+  auto split_2 = TransformationSplitBlock(11, 1, 101);
+  ASSERT_TRUE(split_2.IsApplicable(context.get(), fact_manager));
+  split_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %101
+        %101 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_2, context.get()));
+
+  auto split_3 = TransformationSplitBlock(14, 0, 102);
+  ASSERT_TRUE(split_3.IsApplicable(context.get(), fact_manager));
+  split_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %101
+        %101 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+               OpBranch %102
+        %102 = OpLabel
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_3, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockBeforeSelectBranch) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int x, y;
+  //   x = 2;
+  //   if (x < y) {
+  //     y = 3;
+  //   } else {
+  //     y = 4;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %13 = OpTypeBool
+         %17 = OpConstant %6 3
+         %19 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %18
+         %15 = OpLabel
+               OpStore %11 %17
+               OpBranch %16
+         %18 = OpLabel
+               OpStore %11 %19
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // Illegal to split between the merge and the conditional branch.
+  ASSERT_FALSE(TransformationSplitBlock(14, 2, 100)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(12, 3, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto split = TransformationSplitBlock(14, 1, 100);
+  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
+  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %11 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %13 = OpTypeBool
+         %17 = OpConstant %6 3
+         %19 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpBranch %100
+        %100 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %18
+         %15 = OpLabel
+               OpStore %11 %17
+               OpBranch %16
+         %18 = OpLabel
+               OpStore %11 %19
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockBeforeSwitchBranch) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int x, y;
+  //   switch (y) {
+  //     case 1:
+  //       x = 2;
+  //     case 2:
+  //       break;
+  //     case 3:
+  //       x = 4;
+  //     default:
+  //       x = 6;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "y"
+               OpName %15 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %16 = OpConstant %6 2
+         %18 = OpConstant %6 4
+         %19 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %15 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+               OpSelectionMerge %14 None
+               OpSwitch %9 %13 1 %10 2 %11 3 %12
+         %13 = OpLabel
+               OpStore %15 %19
+               OpBranch %14
+         %10 = OpLabel
+               OpStore %15 %16
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %14
+         %12 = OpLabel
+               OpStore %15 %18
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // Illegal to split between the merge and the conditional branch.
+  ASSERT_FALSE(TransformationSplitBlock(9, 2, 100).IsApplicable(context.get(),
+                                                                fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(15, 3, 100)
+                   .IsApplicable(context.get(), fact_manager));
+
+  auto split = TransformationSplitBlock(9, 1, 100);
+  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
+  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "y"
+               OpName %15 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %16 = OpConstant %6 2
+         %18 = OpConstant %6 4
+         %19 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %15 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+               OpBranch %100
+        %100 = OpLabel
+               OpSelectionMerge %14 None
+               OpSwitch %9 %13 1 %10 2 %11 3 %12
+         %13 = OpLabel
+               OpStore %15 %19
+               OpBranch %14
+         %10 = OpLabel
+               OpStore %15 %16
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %14
+         %12 = OpLabel
+               OpStore %15 %18
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, NoSplitDuringOpPhis) {
+  // The SPIR-V in this test came from the following fragment shader, with
+  // local store elimination applied to get some OpPhi instructions.
+  //
+  // void main() {
+  //   int x;
+  //   int i;
+  //   for (i = 0; i < 100; i++) {
+  //     x += i;
+  //   }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i"
+               OpName %19 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %24 = OpConstant %6 1
+         %28 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+         %27 = OpPhi %6 %28 %5 %22 %13
+         %26 = OpPhi %6 %9 %5 %25 %13
+               OpBranch %50
+         %50 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %26 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %22 = OpIAdd %6 %27 %26
+               OpStore %19 %22
+               OpBranch %13
+         %13 = OpLabel
+         %25 = OpIAdd %6 %26 %24
+               OpStore %8 %25
+               OpBranch %50
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // We cannot split before OpPhi instructions, since the number of incoming
+  // blocks may not appropriately match after splitting.
+  ASSERT_FALSE(TransformationSplitBlock(26, 0, 100)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(27, 0, 100)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSplitBlock(27, 1, 100)
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+	       OpBranch %20
+	 %20 = OpLabel
+	 %21 = OpPhi %6 %11 %5
+         OpStore %10 %21
+         OpReturn
+         OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  ASSERT_TRUE(TransformationSplitBlock(21, 0, 100)
+                  .IsApplicable(context.get(), fact_manager));
+  auto split = TransformationSplitBlock(20, 1, 100);
+  ASSERT_TRUE(split.IsApplicable(context.get(), fact_manager));
+  split.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+	       OpBranch %20
+	 %20 = OpLabel
+         OpBranch %100
+  %100 = OpLabel
+	 %21 = OpPhi %6 %11 %20
+         OpStore %10 %21
+         OpReturn
+         OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/uniform_buffer_element_descriptor_test.cpp b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
new file mode 100644
index 0000000..6c6d52a
--- /dev/null
+++ b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(UniformBufferElementDescriptorTest, TestEquality) {
+  // Test that equality works as expected for various buffer element
+  // descriptors.
+
+  protobufs::UniformBufferElementDescriptor descriptor1 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor2 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor3 =
+      MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor4 =
+      MakeUniformBufferElementDescriptor(1, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor5 =
+      MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor6 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 4});
+  protobufs::UniformBufferElementDescriptor descriptor7 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2});
+
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor1));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor2));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor3, &descriptor3));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor4, &descriptor4));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor5, &descriptor5));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor6, &descriptor6));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor7, &descriptor7));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor3));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor3, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor4));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor4, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor5));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor5, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor6));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor6, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor7));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor7, &descriptor1));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index 69659c6..c565e11 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -37,7 +37,10 @@
       ":spvtools_opt_legalization_fuzzer",
       ":spvtools_opt_performance_fuzzer",
       ":spvtools_opt_size_fuzzer",
+      ":spvtools_opt_webgputovulkan_fuzzer",
+      ":spvtools_opt_vulkantowebgpu_fuzzer",
       ":spvtools_val_fuzzer",
+      ":spvtools_val_webgpu_fuzzer",
     ]
   }
 }
@@ -87,12 +90,31 @@
   ]
 }
 
+
+spvtools_fuzzer("spvtools_opt_webgputovulkan_fuzzer_src") {
+  sources = [
+    "spvtools_opt_webgputovulkan_fuzzer.cpp",
+  ]
+}
+
+spvtools_fuzzer("spvtools_opt_vulkantowebgpu_fuzzer_src") {
+  sources = [
+    "spvtools_opt_vulkantowebgpu_fuzzer.cpp",
+  ]
+}
+
 spvtools_fuzzer("spvtools_val_fuzzer_src") {
   sources = [
     "spvtools_val_fuzzer.cpp",
   ]
 }
 
+spvtools_fuzzer("spvtools_val_webgpu_fuzzer_src") {
+  sources = [
+    "spvtools_val_webgpu_fuzzer.cpp",
+  ]
+}
+
 if (!build_with_chromium || use_fuzzing_engine) {
   fuzzer_test("spvtools_binary_parser_fuzzer") {
     sources = []
@@ -127,6 +149,22 @@
     seed_corpus = "corpora/spv"
   }
 
+  fuzzer_test("spvtools_opt_webgputovulkan_fuzzer") {
+    sources = []
+    deps = [
+      ":spvtools_opt_webgputovulkan_fuzzer_src",
+    ]
+    seed_corpus = "corpora/spv"
+  }
+
+  fuzzer_test("spvtools_opt_vulkantowebgpu_fuzzer") {
+    sources = []
+    deps = [
+      ":spvtools_opt_vulkantowebgpu_fuzzer_src",
+    ]
+    seed_corpus = "corpora/spv"
+  }
+
   fuzzer_test("spvtools_val_fuzzer") {
     sources = []
     deps = [
@@ -134,4 +172,12 @@
     ]
     seed_corpus = "corpora/spv"
   }
+
+  fuzzer_test("spvtools_val_webgpu_fuzzer") {
+    sources = []
+    deps = [
+      ":spvtools_val_webgpu_fuzzer_src",
+    ]
+    seed_corpus = "corpora/spv"
+  }
 }
diff --git a/test/fuzzers/spvtools_opt_vulkantowebgpu_fuzzer.cpp b/test/fuzzers/spvtools_opt_vulkantowebgpu_fuzzer.cpp
new file mode 100644
index 0000000..0b2ecc3
--- /dev/null
+++ b/test/fuzzers/spvtools_opt_vulkantowebgpu_fuzzer.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2019 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 <cstdint>
+#include <vector>
+
+#include "spirv-tools/optimizer.hpp"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0);
+  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.RegisterVulkanToWebGPUPasses();
+  optimizer.Run(input.data(), input.size(), &input);
+
+  return 0;
+}
diff --git a/test/fuzzers/spvtools_opt_webgputovulkan_fuzzer.cpp b/test/fuzzers/spvtools_opt_webgputovulkan_fuzzer.cpp
new file mode 100644
index 0000000..1e44857
--- /dev/null
+++ b/test/fuzzers/spvtools_opt_webgputovulkan_fuzzer.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2019 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 <cstdint>
+#include <vector>
+
+#include "spirv-tools/optimizer.hpp"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1);
+  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.RegisterWebGPUToVulkanPasses();
+  optimizer.Run(input.data(), input.size(), &input);
+
+  return 0;
+}
diff --git a/test/fuzzers/spvtools_val_webgpu_fuzzer.cpp b/test/fuzzers/spvtools_val_webgpu_fuzzer.cpp
new file mode 100644
index 0000000..bed6e1a
--- /dev/null
+++ b/test/fuzzers/spvtools_val_webgpu_fuzzer.cpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 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 <cstdint>
+#include <vector>
+
+#include "spirv-tools/libspirv.hpp"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  spvtools::SpirvTools tools(SPV_ENV_WEBGPU_0);
+  tools.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;
+  }
+
+  tools.Validate(input);
+  return 0;
+}
diff --git a/test/generator_magic_number_test.cpp b/test/generator_magic_number_test.cpp
index bc5fdf5..7131ac4 100644
--- a/test/generator_magic_number_test.cpp
+++ b/test/generator_magic_number_test.cpp
@@ -34,7 +34,7 @@
               GetParam().name());
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Registered, GeneratorMagicNumberTest,
     ::testing::ValuesIn(std::vector<EnumCase<spv_generator_t>>{
         {SPV_GENERATOR_KHRONOS, "Khronos"},
@@ -47,16 +47,16 @@
          "Khronos LLVM/SPIR-V Translator"},
         {SPV_GENERATOR_KHRONOS_ASSEMBLER, "Khronos SPIR-V Tools Assembler"},
         {SPV_GENERATOR_KHRONOS_GLSLANG, "Khronos Glslang Reference Front End"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Unregistered, GeneratorMagicNumberTest,
     ::testing::ValuesIn(std::vector<EnumCase<spv_generator_t>>{
         // We read registered entries from the SPIR-V XML Registry file
         // which can change over time.
         {spv_generator_t(1000), "Unknown"},
         {spv_generator_t(9999), "Unknown"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp
index 8745060..c422f75 100644
--- a/test/hex_float_test.cpp
+++ b/test/hex_float_test.cpp
@@ -81,7 +81,7 @@
   EXPECT_THAT(Decode<double>(GetParam().second), Eq(GetParam().first));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32Tests, HexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         {0.f, "0x0p+0"},
@@ -131,9 +131,9 @@
         {float(ldexp(1.0, -127) / 2.0 + (ldexp(1.0, -127) / 4.0f)),
          "0x1.8p-128"},
 
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32NanTests, HexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         // Various NAN and INF cases
@@ -149,9 +149,9 @@
         {uint32_t(0x7f800c00), "0x1.0018p+128"},     // +nan
         {uint32_t(0x7F80F000), "0x1.01ep+128"},      // +nan
         {uint32_t(0x7FFFFFFF), "0x1.fffffep+128"},   // +nan
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64Tests, HexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<FloatProxy<double>, std::string>>({
@@ -222,9 +222,9 @@
             {ldexp(1.0, -1023) / 2.0 + (ldexp(1.0, -1023) / 4.0),
              "0x1.8p-1024"},
 
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64NanTests, HexDoubleTest,
     ::testing::ValuesIn(std::vector<
                         std::pair<FloatProxy<double>, std::string>>({
@@ -241,7 +241,7 @@
         {uint64_t(0x7FF0000000000001LL), "0x1.0000000000001p+1024"},   // -nan
         {uint64_t(0x7FF0000300000000LL), "0x1.00003p+1024"},           // -nan
         {uint64_t(0x7FFFFFFFFFFFFFFFLL), "0x1.fffffffffffffp+1024"},   // -nan
-    })), );
+    })));
 
 // Tests that encoding a value and decoding it again restores
 // the same value.
@@ -263,14 +263,14 @@
   EXPECT_THAT(GetParam(), Eq(res.getAsFloat()));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32StoreTests, RoundTripFloatTest,
     ::testing::ValuesIn(std::vector<float>(
         {// Value requiring more than 6 digits of precision to be
          // represented accurately.
          3.0000002f})));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64StoreTests, RoundTripDoubleTest,
     ::testing::ValuesIn(std::vector<double>(
         {// Value requiring more than 15 digits of precision to be
@@ -300,7 +300,7 @@
   EXPECT_THAT(Decode<double>(GetParam().first), Eq(GetParam().second));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32DecodeTests, DecodeHexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, FloatProxy<float>>>({
         {"0x0p+000", 0.f},
@@ -320,9 +320,9 @@
         {"0xFFp+0", 255.f},
         {"0x0.8p+0", 0.5f},
         {"0x0.4p+0", 0.25f},
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32DecodeInfTests, DecodeHexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, FloatProxy<float>>>({
         // inf cases
@@ -330,9 +330,9 @@
         {"0x32p+127", uint32_t(0x7F800000)},   // inf
         {"0x32p+500", uint32_t(0x7F800000)},   // inf
         {"-0x32p+127", uint32_t(0xFF800000)},  // -inf
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64DecodeTests, DecodeHexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<std::string, FloatProxy<double>>>({
@@ -353,9 +353,9 @@
             {"0xFFp+0", 255.},
             {"0x0.8p+0", 0.5},
             {"0x0.4p+0", 0.25},
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64DecodeInfTests, DecodeHexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<std::string, FloatProxy<double>>>({
@@ -364,7 +364,7 @@
             {"0x32p+1023", uint64_t(0x7FF0000000000000)},   // inf
             {"0x32p+5000", uint64_t(0x7FF0000000000000)},   // inf
             {"-0x32p+1023", uint64_t(0xFFF0000000000000)},  // -inf
-        })), );
+        })));
 
 TEST(FloatProxy, ValidConversion) {
   EXPECT_THAT(FloatProxy<float>(1.f).getAsFloat(), Eq(1.0f));
@@ -503,7 +503,7 @@
       Eq(GetParam().second));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32Tests, FloatProxyFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         // Zero
@@ -533,9 +533,9 @@
 
         {std::numeric_limits<float>::infinity(), "0x1p+128"},
         {-std::numeric_limits<float>::infinity(), "-0x1p+128"},
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64Tests, FloatProxyDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<FloatProxy<double>, std::string>>({
@@ -570,7 +570,7 @@
             {std::numeric_limits<double>::infinity(), "0x1p+1024"},
             {-std::numeric_limits<double>::infinity(), "-0x1p+1024"},
 
-        })), );
+        })));
 
 // double is used so that unbiased_exponent can be used with the output
 // of ldexp directly.
@@ -792,7 +792,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(F32ToF16, HexFloatRoundTest,
+INSTANTIATE_TEST_SUITE_P(F32ToF16, HexFloatRoundTest,
   ::testing::ValuesIn(std::vector<RoundSignificandCase>(
   {
     {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToZero},
@@ -838,7 +838,7 @@
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -129)), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity},
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -131)), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity},
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -130)), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven},
-  })),);
+  })));
 // clang-format on
 
 struct UpCastSignificandCase {
@@ -872,7 +872,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F16toF32, HexFloatRoundUpSignificandTest,
     // 0xFC00 of the source 16-bit hex value cover the sign and the exponent.
     // They are ignored for this test.
@@ -881,7 +881,7 @@
         {0x0F00, 0x600000},
         {0x0F01, 0x602000},
         {0x0FFF, 0x7FE000},
-    })), );
+    })));
 
 struct DownCastTest {
   float source_float;
@@ -923,7 +923,7 @@
 const uint16_t positive_infinity = 0x7C00;
 const uint16_t negative_infinity = 0xFC00;
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F32ToF16, HexFloatFP32To16Tests,
     ::testing::ValuesIn(std::vector<DownCastTest>({
         // Exactly representable as half.
@@ -1014,7 +1014,7 @@
           RD::kToNearestEven}},
 
         // Nans are below because we cannot test for equality.
-    })), );
+    })));
 
 struct UpCastCase {
   uint16_t source_half;
@@ -1043,7 +1043,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F16ToF32, HexFloatFP16To32Tests,
     ::testing::ValuesIn(std::vector<UpCastCase>({
         {0x0000, 0.f},
@@ -1064,7 +1064,7 @@
         // inf
         {0x7C00, std::numeric_limits<float>::infinity()},
         {0xFC00, -std::numeric_limits<float>::infinity()},
-    })), );
+    })));
 
 TEST(HexFloatOperationTests, NanTests) {
   using HF = HexFloat<FloatProxy<float>>;
@@ -1137,7 +1137,7 @@
   return FloatParseCase<T>{literal, negate_value, true, proxy_expected_value};
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatParse, ParseNormalFloatTest,
     ::testing::ValuesIn(std::vector<FloatParseCase<float>>{
         // Failing cases due to trivially incorrect syntax.
@@ -1169,7 +1169,7 @@
         // We can't have -1e40 and negate_value == true since
         // that represents an original case of "--1e40" which
         // is invalid.
-    }), );
+    }));
 
 using ParseNormalFloat16Test =
     ::testing::TestWithParam<FloatParseCase<Float16>>;
@@ -1188,7 +1188,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float16Parse, ParseNormalFloat16Test,
     ::testing::ValuesIn(std::vector<FloatParseCase<Float16>>{
         // Failing cases due to trivially incorrect syntax.
@@ -1212,7 +1212,7 @@
         BadFloatParseCase<Float16>("-2.0", true, uint16_t{0}),
         BadFloatParseCase<Float16>("+0.0", true, uint16_t{0}),
         BadFloatParseCase<Float16>("+2.0", true, uint16_t{0}),
-    }), );
+    }));
 
 // A test case for detecting infinities.
 template <typename T>
@@ -1235,7 +1235,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatOverflow, FloatProxyParseOverflowFloatTest,
     ::testing::ValuesIn(std::vector<OverflowParseCase<float>>({
         {"0", true, 0.0f},
@@ -1247,7 +1247,7 @@
         {"-1e40", false, -FLT_MAX},
         {"1e400", false, FLT_MAX},
         {"-1e400", false, -FLT_MAX},
-    })), );
+    })));
 
 using FloatProxyParseOverflowDoubleTest =
     ::testing::TestWithParam<OverflowParseCase<double>>;
@@ -1262,7 +1262,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DoubleOverflow, FloatProxyParseOverflowDoubleTest,
     ::testing::ValuesIn(std::vector<OverflowParseCase<double>>({
         {"0", true, 0.0},
@@ -1274,7 +1274,7 @@
         {"-1e40", true, -1e40},
         {"1e400", false, DBL_MAX},
         {"-1e400", false, -DBL_MAX},
-    })), );
+    })));
 
 using FloatProxyParseOverflowFloat16Test =
     ::testing::TestWithParam<OverflowParseCase<uint16_t>>;
@@ -1291,7 +1291,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float16Overflow, FloatProxyParseOverflowFloat16Test,
     ::testing::ValuesIn(std::vector<OverflowParseCase<uint16_t>>({
         {"0", true, uint16_t{0}},
@@ -1305,7 +1305,7 @@
         {"-1e38", false, uint16_t{0xfbff}},
         {"-1e40", false, uint16_t{0xfbff}},
         {"-1e400", false, uint16_t{0xfbff}},
-    })), );
+    })));
 
 TEST(FloatProxy, Max) {
   EXPECT_THAT(FloatProxy<Float16>::max().getAsFloat().get_value(),
diff --git a/test/huffman_codec.cpp b/test/huffman_codec.cpp
deleted file mode 100644
index 58a7810..0000000
--- a/test/huffman_codec.cpp
+++ /dev/null
@@ -1,317 +0,0 @@
-// 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 <algorithm>
-#include <map>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "source/comp/bit_stream.h"
-#include "source/comp/huffman_codec.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-const std::map<std::string, uint32_t>& GetTestSet() {
-  static const std::map<std::string, uint32_t> hist = {
-      {"a", 4}, {"e", 7}, {"f", 3}, {"h", 2}, {"i", 3},
-      {"m", 2}, {"n", 2}, {"s", 2}, {"t", 2}, {"l", 1},
-      {"o", 2}, {"p", 1}, {"r", 1}, {"u", 1}, {"x", 1},
-  };
-
-  return hist;
-}
-
-class TestBitReader {
- public:
-  TestBitReader(const std::string& bits) : bits_(bits) {}
-
-  bool ReadBit(bool* bit) {
-    if (pos_ < bits_.length()) {
-      *bit = bits_[pos_++] == '0' ? false : true;
-      return true;
-    }
-    return false;
-  }
-
- private:
-  std::string bits_;
-  size_t pos_ = 0;
-};
-
-TEST(Huffman, PrintTree) {
-  HuffmanCodec<std::string> huffman(GetTestSet());
-  std::stringstream ss;
-  huffman.PrintTree(ss);
-
-  // clang-format off
-  const std::string expected = std::string(R"(
-15-----7------e
-       8------4------a
-              4------2------m
-                     2------n
-19-----8------4------2------o
-                     2------s
-              4------2------t
-                     2------1------l
-                            1------p
-       11-----5------2------1------r
-                            1------u
-                     3------f
-              6------3------i
-                     3------1------x
-                            2------h
-)").substr(1);
-  // clang-format on
-
-  EXPECT_EQ(expected, ss.str());
-}
-
-TEST(Huffman, PrintTable) {
-  HuffmanCodec<std::string> huffman(GetTestSet());
-  std::stringstream ss;
-  huffman.PrintTable(ss);
-
-  const std::string expected = std::string(R"(
-e 7 11
-a 4 101
-i 3 0001
-f 3 0010
-t 2 0101
-s 2 0110
-o 2 0111
-n 2 1000
-m 2 1001
-h 2 00000
-x 1 00001
-u 1 00110
-r 1 00111
-p 1 01000
-l 1 01001
-)")
-                                   .substr(1);
-
-  EXPECT_EQ(expected, ss.str());
-}
-
-TEST(Huffman, TestValidity) {
-  HuffmanCodec<std::string> huffman(GetTestSet());
-  const auto& encoding_table = huffman.GetEncodingTable();
-  std::vector<std::string> codes;
-  for (const auto& entry : encoding_table) {
-    codes.push_back(BitsToStream(entry.second.first, entry.second.second));
-  }
-
-  std::sort(codes.begin(), codes.end());
-
-  ASSERT_LT(codes.size(), 20u) << "Inefficient test ahead";
-
-  for (size_t i = 0; i < codes.size(); ++i) {
-    for (size_t j = i + 1; j < codes.size(); ++j) {
-      ASSERT_FALSE(codes[i] == codes[j].substr(0, codes[i].length()))
-          << codes[i] << " is prefix of " << codes[j];
-    }
-  }
-}
-
-TEST(Huffman, TestEncode) {
-  HuffmanCodec<std::string> huffman(GetTestSet());
-
-  uint64_t bits = 0;
-  size_t num_bits = 0;
-
-  EXPECT_TRUE(huffman.Encode("e", &bits, &num_bits));
-  EXPECT_EQ(2u, num_bits);
-  EXPECT_EQ("11", BitsToStream(bits, num_bits));
-
-  EXPECT_TRUE(huffman.Encode("a", &bits, &num_bits));
-  EXPECT_EQ(3u, num_bits);
-  EXPECT_EQ("101", BitsToStream(bits, num_bits));
-
-  EXPECT_TRUE(huffman.Encode("x", &bits, &num_bits));
-  EXPECT_EQ(5u, num_bits);
-  EXPECT_EQ("00001", BitsToStream(bits, num_bits));
-
-  EXPECT_FALSE(huffman.Encode("y", &bits, &num_bits));
-}
-
-TEST(Huffman, TestDecode) {
-  HuffmanCodec<std::string> huffman(GetTestSet());
-  TestBitReader bit_reader(
-      "01001"
-      "0001"
-      "1000"
-      "00110"
-      "00001"
-      "00");
-  auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); };
-
-  std::string decoded;
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ("l", decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ("i", decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ("n", decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ("u", decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ("x", decoded);
-
-  ASSERT_FALSE(huffman.DecodeFromStream(read_bit, &decoded));
-}
-
-TEST(Huffman, TestDecodeNumbers) {
-  const std::map<uint32_t, uint32_t> hist = {{1, 10}, {2, 5}, {3, 15}};
-  HuffmanCodec<uint32_t> huffman(hist);
-
-  TestBitReader bit_reader(
-      "1"
-      "1"
-      "01"
-      "00"
-      "01"
-      "1");
-  auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); };
-
-  uint32_t decoded;
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(3u, decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(3u, decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(2u, decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(1u, decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(2u, decoded);
-
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(3u, decoded);
-}
-
-TEST(Huffman, SerializeToTextU64) {
-  const std::map<uint64_t, uint32_t> hist = {{1001, 10}, {1002, 5}, {1003, 15}};
-  HuffmanCodec<uint64_t> huffman(hist);
-
-  const std::string code = huffman.SerializeToText(2);
-
-  const std::string expected = R"((5, {
-    {0, 0, 0},
-    {1001, 0, 0},
-    {1002, 0, 0},
-    {1003, 0, 0},
-    {0, 1, 2},
-    {0, 4, 3},
-  }))";
-
-  ASSERT_EQ(expected, code);
-}
-
-TEST(Huffman, SerializeToTextString) {
-  const std::map<std::string, uint32_t> hist = {
-      {"aaa", 10}, {"bbb", 20}, {"ccc", 15}};
-  HuffmanCodec<std::string> huffman(hist);
-
-  const std::string code = huffman.SerializeToText(4);
-
-  const std::string expected = R"((5, {
-      {"", 0, 0},
-      {"aaa", 0, 0},
-      {"bbb", 0, 0},
-      {"ccc", 0, 0},
-      {"", 3, 1},
-      {"", 4, 2},
-    }))";
-
-  ASSERT_EQ(expected, code);
-}
-
-TEST(Huffman, CreateFromTextString) {
-  std::vector<HuffmanCodec<std::string>::Node> nodes = {
-      {},
-      {"root", 2, 3},
-      {"left", 0, 0},
-      {"right", 0, 0},
-  };
-
-  HuffmanCodec<std::string> huffman(1, std::move(nodes));
-
-  std::stringstream ss;
-  huffman.PrintTree(ss);
-
-  const std::string expected = std::string(R"(
-0------right
-0------left
-)")
-                                   .substr(1);
-
-  EXPECT_EQ(expected, ss.str());
-}
-
-TEST(Huffman, CreateFromTextU64) {
-  HuffmanCodec<uint64_t> huffman(5, {
-                                        {0, 0, 0},
-                                        {1001, 0, 0},
-                                        {1002, 0, 0},
-                                        {1003, 0, 0},
-                                        {0, 1, 2},
-                                        {0, 4, 3},
-                                    });
-
-  std::stringstream ss;
-  huffman.PrintTree(ss);
-
-  const std::string expected = std::string(R"(
-0------1003
-0------0------1002
-       0------1001
-)")
-                                   .substr(1);
-
-  EXPECT_EQ(expected, ss.str());
-
-  TestBitReader bit_reader("01");
-  auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); };
-
-  uint64_t decoded = 0;
-  ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded));
-  EXPECT_EQ(1002u, decoded);
-
-  uint64_t bits = 0;
-  size_t num_bits = 0;
-
-  EXPECT_TRUE(huffman.Encode(1001, &bits, &num_bits));
-  EXPECT_EQ(2u, num_bits);
-  EXPECT_EQ("00", BitsToStream(bits, num_bits));
-}
-
-}  // namespace
-}  // namespace comp
-}  // namespace spvtools
diff --git a/test/link/CMakeLists.txt b/test/link/CMakeLists.txt
index 06aeb91..ee41b91 100644
--- a/test/link/CMakeLists.txt
+++ b/test/link/CMakeLists.txt
@@ -23,5 +23,6 @@
        memory_model_test.cpp
        partial_linkage_test.cpp
        unique_ids_test.cpp
+       type_match_test.cpp
   LIBS SPIRV-Tools-opt SPIRV-Tools-link
 )
diff --git a/test/link/linker_fixture.h b/test/link/linker_fixture.h
index 303f1bf..7bb1223 100644
--- a/test/link/linker_fixture.h
+++ b/test/link/linker_fixture.h
@@ -19,6 +19,8 @@
 #include <string>
 #include <vector>
 
+#include "effcee/effcee.h"
+#include "re2/re2.h"
 #include "source/spirv_constant.h"
 #include "spirv-tools/linker.hpp"
 #include "test/unit_spirv.h"
@@ -80,6 +82,101 @@
     return spvtools::Link(context_, binaries, linked_binary, options);
   }
 
+  // Assembles and links a vector of SPIR-V bodies based on the |templateBody|.
+  // Template arguments to be replaced are written as {a,b,...}.
+  // SPV_ERROR_INVALID_TEXT is returned if the assembling failed for any of the
+  // resulting bodies (or errors in the template), and SPV_ERROR_INVALID_POINTER
+  // if |linked_binary| is a null pointer.
+  spv_result_t ExpandAndLink(
+      const std::string& templateBody, spvtest::Binary* linked_binary,
+      spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    if (!linked_binary) return SPV_ERROR_INVALID_POINTER;
+
+    // Find out how many template arguments there are, we assume they all have
+    // the same number. We'll error later if they don't.
+    re2::StringPiece temp(templateBody);
+    re2::StringPiece x;
+    int cnt = 0;
+    if (!RE2::FindAndConsume(&temp, "{")) return SPV_ERROR_INVALID_TEXT;
+    while (RE2::FindAndConsume(&temp, "([,}])", &x) && x[0] == ',') cnt++;
+    cnt++;
+    if (cnt <= 1) return SPV_ERROR_INVALID_TEXT;
+
+    // Construct a regex for a single common strip and template expansion.
+    std::string regex("([^{]*){");
+    for (int i = 0; i < cnt; i++) regex += (i > 0) ? ",([^,]*)" : "([^,]*)";
+    regex += "}";
+    RE2 pattern(regex);
+
+    // Prepare the RE2::Args for processing.
+    re2::StringPiece common;
+    std::vector<re2::StringPiece> variants(cnt);
+    std::vector<RE2::Arg> args(cnt + 1);
+    args[0] = RE2::Arg(&common);
+    std::vector<RE2::Arg*> pargs(cnt + 1);
+    pargs[0] = &args[0];
+    for (int i = 0; i < cnt; i++) {
+      args[i + 1] = RE2::Arg(&variants[i]);
+      pargs[i + 1] = &args[i + 1];
+    }
+
+    // Reset and construct the bodies bit by bit.
+    std::vector<std::string> bodies(cnt);
+    re2::StringPiece temp2(templateBody);
+    while (RE2::ConsumeN(&temp2, pattern, pargs.data(), cnt + 1)) {
+      for (int i = 0; i < cnt; i++) {
+        bodies[i].append(common.begin(), common.end());
+        bodies[i].append(variants[i].begin(), variants[i].end());
+      }
+    }
+    RE2::Consume(&temp2, "([^{]*)", &common);
+    for (int i = 0; i < cnt; i++)
+      bodies[i].append(common.begin(), common.end());
+
+    // Run through the assemble and link stages of the process.
+    return AssembleAndLink(bodies, linked_binary, options);
+  }
+
+  // Expand the |templateBody| and link the results as with ExpandAndLink,
+  // then disassemble and test that the result matches the |expected|.
+  void ExpandAndCheck(
+      const std::string& templateBody, const std::string& expected,
+      const spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    spvtest::Binary linked_binary;
+    spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options);
+    EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n"
+                                << templateBody;
+    if (res == SPV_SUCCESS) {
+      std::string result;
+      EXPECT_TRUE(
+          tools_.Disassemble(linked_binary, &result, disassemble_options_))
+          << GetErrorMessage();
+      EXPECT_EQ(expected, result);
+    }
+  }
+
+  // An alternative to ExpandAndCheck, which uses the |templateBody| as the
+  // match pattern for the disassembled linked result.
+  void ExpandAndMatch(
+      const std::string& templateBody,
+      const spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    spvtest::Binary linked_binary;
+    spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options);
+    EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n"
+                                << templateBody;
+    if (res == SPV_SUCCESS) {
+      std::string result;
+      EXPECT_TRUE(
+          tools_.Disassemble(linked_binary, &result, disassemble_options_))
+          << GetErrorMessage();
+      auto match_res = effcee::Match(result, templateBody);
+      EXPECT_EQ(effcee::Result::Status::Ok, match_res.status())
+          << match_res.message() << "\nExpanded from:\n"
+          << templateBody << "\nChecking result:\n"
+          << result;
+    }
+  }
+
   // Links the given SPIR-V binaries together; SPV_ERROR_INVALID_POINTER is
   // returned if |linked_binary| is a null pointer.
   spv_result_t Link(
diff --git a/test/link/type_match_test.cpp b/test/link/type_match_test.cpp
new file mode 100644
index 0000000..c12ffb3
--- /dev/null
+++ b/test/link/type_match_test.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) 2019 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 "gmock/gmock.h"
+#include "test/link/linker_fixture.h"
+
+namespace spvtools {
+namespace {
+
+using TypeMatch = spvtest::LinkerTest;
+
+// Basic types
+#define PartInt(D, N) D(N) " = OpTypeInt 32 0"
+#define PartFloat(D, N) D(N) " = OpTypeFloat 32"
+#define PartOpaque(D, N) D(N) " = OpTypeOpaque \"bar\""
+#define PartSampler(D, N) D(N) " = OpTypeSampler"
+#define PartEvent(D, N) D(N) " = OpTypeEvent"
+#define PartDeviceEvent(D, N) D(N) " = OpTypeDeviceEvent"
+#define PartReserveId(D, N) D(N) " = OpTypeReserveId"
+#define PartQueue(D, N) D(N) " = OpTypeQueue"
+#define PartPipe(D, N) D(N) " = OpTypePipe ReadWrite"
+#define PartPipeStorage(D, N) D(N) " = OpTypePipeStorage"
+#define PartNamedBarrier(D, N) D(N) " = OpTypeNamedBarrier"
+
+// Compound types
+#define PartVector(DR, DA, N, T) DR(N) " = OpTypeVector " DA(T) " 3"
+#define PartMatrix(DR, DA, N, T) DR(N) " = OpTypeMatrix " DA(T) " 4"
+#define PartImage(DR, DA, N, T) \
+  DR(N) " = OpTypeImage " DA(T) " 2D 0 0 0 0 Rgba32f"
+#define PartSampledImage(DR, DA, N, T) DR(N) " = OpTypeSampledImage " DA(T)
+#define PartArray(DR, DA, N, T) DR(N) " = OpTypeArray " DA(T) " " DA(const)
+#define PartRuntimeArray(DR, DA, N, T) DR(N) " = OpTypeRuntimeArray " DA(T)
+#define PartStruct(DR, DA, N, T) DR(N) " = OpTypeStruct " DA(T) " " DA(T)
+#define PartPointer(DR, DA, N, T) DR(N) " = OpTypePointer Workgroup " DA(T)
+#define PartFunction(DR, DA, N, T) DR(N) " = OpTypeFunction " DA(T) " " DA(T)
+
+#define CheckDecoRes(S) "[[" #S ":%\\w+]]"
+#define CheckDecoArg(S) "[[" #S "]]"
+#define InstDeco(S) "%" #S
+
+#define MatchPart1(F, N) \
+  "; CHECK: " Part##F(CheckDecoRes, N) "\n" Part##F(InstDeco, N) "\n"
+#define MatchPart2(F, N, T)                                           \
+  "; CHECK: " Part##F(CheckDecoRes, CheckDecoArg, N, T) "\n" Part##F( \
+      InstDeco, InstDeco, N, T) "\n"
+
+#define MatchF(N, CODE)                                         \
+  TEST_F(TypeMatch, N) {                                        \
+    const std::string base =                                    \
+        "OpCapability Linkage\n"                                \
+        "OpCapability NamedBarrier\n"                           \
+        "OpCapability PipeStorage\n"                            \
+        "OpCapability Pipes\n"                                  \
+        "OpCapability DeviceEnqueue\n"                          \
+        "OpCapability Kernel\n"                                 \
+        "OpCapability Shader\n"                                 \
+        "OpCapability Addresses\n"                              \
+        "OpDecorate %var LinkageAttributes \"foo\" "            \
+        "{Import,Export}\n"                                     \
+        "; CHECK: [[baseint:%\\w+]] = OpTypeInt 32 1\n"         \
+        "%baseint = OpTypeInt 32 1\n"                           \
+        "; CHECK: [[const:%\\w+]] = OpConstant [[baseint]] 3\n" \
+        "%const = OpConstant %baseint 3\n" CODE                 \
+        "; CHECK: OpVariable [[type]] Uniform\n"                \
+        "%var = OpVariable %type Uniform";                      \
+    ExpandAndMatch(base);                                       \
+  }
+
+#define Match1(T) MatchF(Type##T, MatchPart1(T, type))
+#define Match2(T, A) \
+  MatchF(T##OfType##A, MatchPart1(A, a) MatchPart2(T, type, a))
+#define Match3(T, A, B)   \
+  MatchF(T##Of##A##Of##B, \
+         MatchPart1(B, b) MatchPart2(A, a, b) MatchPart2(T, type, a))
+
+// Basic types
+Match1(Int);
+Match1(Float);
+Match1(Opaque);
+Match1(Sampler);
+Match1(Event);
+Match1(DeviceEvent);
+Match1(ReserveId);
+Match1(Queue);
+Match1(Pipe);
+Match1(PipeStorage);
+Match1(NamedBarrier);
+
+// Simpler (restricted) compound types
+Match2(Vector, Float);
+Match3(Matrix, Vector, Float);
+Match2(Image, Float);
+
+// Unrestricted compound types
+#define MatchCompounds1(A) \
+  Match2(RuntimeArray, A); \
+  Match2(Struct, A);       \
+  Match2(Pointer, A);      \
+  Match2(Function, A);     \
+  Match2(Array, A);
+#define MatchCompounds2(A, B) \
+  Match3(RuntimeArray, A, B); \
+  Match3(Struct, A, B);       \
+  Match3(Pointer, A, B);      \
+  Match3(Function, A, B);     \
+  Match3(Array, A, B);
+
+MatchCompounds1(Float);
+MatchCompounds2(Array, Float);
+MatchCompounds2(RuntimeArray, Float);
+MatchCompounds2(Struct, Float);
+MatchCompounds2(Pointer, Float);
+MatchCompounds2(Function, Float);
+
+// ForwardPointer tests, which don't fit into the previous mold
+#define MatchFpF(N, CODE)                                             \
+  MatchF(N,                                                           \
+         "; CHECK: OpTypeForwardPointer [[type:%\\w+]] Workgroup\n"   \
+         "OpTypeForwardPointer %type Workgroup\n" CODE                \
+         "; CHECK: [[type]] = OpTypePointer Workgroup [[realtype]]\n" \
+         "%type = OpTypePointer Workgroup %realtype\n")
+#define MatchFp1(T) MatchFpF(ForwardPointerOf##T, MatchPart1(T, realtype))
+#define MatchFp2(T, A) \
+  MatchFpF(ForwardPointerOf##T, MatchPart1(A, a) MatchPart2(T, realtype, a))
+
+MatchFp1(Float);
+MatchFp2(Array, Float);
+MatchFp2(RuntimeArray, Float);
+MatchFp2(Struct, Float);
+MatchFp2(Function, Float);
+
+}  // namespace
+}  // namespace spvtools
diff --git a/test/move_to_front_test.cpp b/test/move_to_front_test.cpp
deleted file mode 100644
index c95d386..0000000
--- a/test/move_to_front_test.cpp
+++ /dev/null
@@ -1,828 +0,0 @@
-// 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 <algorithm>
-#include <iostream>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "source/comp/move_to_front.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-// Class used to test the inner workings of MoveToFront.
-class MoveToFrontTester : public MoveToFront {
- public:
-  // Inserts the value in the internal tree data structure. For testing only.
-  void TestInsert(uint32_t val) { InsertNode(CreateNode(val, val)); }
-
-  // Removes the value from the internal tree data structure. For testing only.
-  void TestRemove(uint32_t val) {
-    const auto it = value_to_node_.find(val);
-    assert(it != value_to_node_.end());
-    RemoveNode(it->second);
-  }
-
-  // Prints the internal tree data structure to |out|. For testing only.
-  void PrintTree(std::ostream& out, bool print_timestamp = false) const {
-    if (root_) PrintTreeInternal(out, root_, 1, print_timestamp);
-  }
-
-  // Returns node handle corresponding to the value. The value may not be in the
-  // tree.
-  uint32_t GetNodeHandle(uint32_t value) const {
-    const auto it = value_to_node_.find(value);
-    if (it == value_to_node_.end()) return 0;
-
-    return it->second;
-  }
-
-  // Returns total node count (both those in the tree and removed,
-  // but not the NIL singleton).
-  size_t GetTotalNodeCount() const {
-    assert(nodes_.size());
-    return nodes_.size() - 1;
-  }
-
-  uint32_t GetLastAccessedValue() const { return last_accessed_value_; }
-
- private:
-  // Prints the internal tree data structure for debug purposes in the following
-  // format:
-  // 10H3S4----5H1S1-----D2
-  //           15H2S2----12H1S1----D3
-  // Right links are horizontal, left links step down one line.
-  // 5H1S1 is read as value 5, height 1, size 1. Optionally node label can also
-  // contain timestamp (5H1S1T15). D3 stands for depth 3.
-  void PrintTreeInternal(std::ostream& out, uint32_t node, size_t depth,
-                         bool print_timestamp) const;
-};
-
-void MoveToFrontTester::PrintTreeInternal(std::ostream& out, uint32_t node,
-                                          size_t depth,
-                                          bool print_timestamp) const {
-  if (!node) {
-    out << "D" << depth - 1 << std::endl;
-    return;
-  }
-
-  const size_t kTextFieldWvaluethWithoutTimestamp = 10;
-  const size_t kTextFieldWvaluethWithTimestamp = 14;
-  const size_t text_field_wvalueth = print_timestamp
-                                         ? kTextFieldWvaluethWithTimestamp
-                                         : kTextFieldWvaluethWithoutTimestamp;
-
-  std::stringstream label;
-  label << ValueOf(node) << "H" << HeightOf(node) << "S" << SizeOf(node);
-  if (print_timestamp) label << "T" << TimestampOf(node);
-  const size_t label_length = label.str().length();
-  if (label_length < text_field_wvalueth)
-    label << std::string(text_field_wvalueth - label_length, '-');
-
-  out << label.str();
-
-  PrintTreeInternal(out, RightOf(node), depth + 1, print_timestamp);
-
-  if (LeftOf(node)) {
-    out << std::string(depth * text_field_wvalueth, ' ');
-    PrintTreeInternal(out, LeftOf(node), depth + 1, print_timestamp);
-  }
-}
-
-void CheckTree(const MoveToFrontTester& mtf, const std::string& expected,
-               bool print_timestamp = false) {
-  std::stringstream ss;
-  mtf.PrintTree(ss, print_timestamp);
-  EXPECT_EQ(expected, ss.str());
-}
-
-TEST(MoveToFront, EmptyTree) {
-  MoveToFrontTester mtf;
-  CheckTree(mtf, std::string());
-}
-
-TEST(MoveToFront, InsertLeftRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(30);
-  mtf.TestInsert(20);
-
-  CheckTree(mtf, std::string(R"(
-30H2S2----20H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestInsert(10);
-  CheckTree(mtf, std::string(R"(
-20H2S3----10H1S1----D2
-          30H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, InsertRightRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(20);
-
-  CheckTree(mtf, std::string(R"(
-10H2S2----D1
-          20H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestInsert(30);
-  CheckTree(mtf, std::string(R"(
-20H2S3----10H1S1----D2
-          30H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, InsertRightLeftRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(30);
-  mtf.TestInsert(20);
-
-  CheckTree(mtf, std::string(R"(
-30H2S2----20H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestInsert(25);
-  CheckTree(mtf, std::string(R"(
-25H2S3----20H1S1----D2
-          30H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, InsertLeftRightRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(20);
-
-  CheckTree(mtf, std::string(R"(
-10H2S2----D1
-          20H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestInsert(15);
-  CheckTree(mtf, std::string(R"(
-15H2S3----10H1S1----D2
-          20H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RemoveSingleton) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  CheckTree(mtf, std::string(R"(
-10H1S1----D1
-)")
-                     .substr(1));
-
-  mtf.TestRemove(10);
-  CheckTree(mtf, "");
-}
-
-TEST(MoveToFront, RemoveRootWithScapegoat) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(5);
-  mtf.TestInsert(15);
-  CheckTree(mtf, std::string(R"(
-10H2S3----5H1S1-----D2
-          15H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestRemove(10);
-  CheckTree(mtf, std::string(R"(
-15H2S2----5H1S1-----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RemoveRightRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(5);
-  mtf.TestInsert(15);
-  mtf.TestInsert(20);
-  CheckTree(mtf, std::string(R"(
-10H3S4----5H1S1-----D2
-          15H2S2----D2
-                    20H1S1----D3
-)")
-                     .substr(1));
-
-  mtf.TestRemove(5);
-
-  CheckTree(mtf, std::string(R"(
-15H2S3----10H1S1----D2
-          20H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RemoveLeftRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(15);
-  mtf.TestInsert(5);
-  mtf.TestInsert(1);
-  CheckTree(mtf, std::string(R"(
-10H3S4----5H2S2-----1H1S1-----D3
-          15H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestRemove(15);
-
-  CheckTree(mtf, std::string(R"(
-5H2S3-----1H1S1-----D2
-          10H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RemoveLeftRightRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(15);
-  mtf.TestInsert(5);
-  mtf.TestInsert(12);
-  CheckTree(mtf, std::string(R"(
-10H3S4----5H1S1-----D2
-          15H2S2----12H1S1----D3
-)")
-                     .substr(1));
-
-  mtf.TestRemove(5);
-
-  CheckTree(mtf, std::string(R"(
-12H2S3----10H1S1----D2
-          15H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RemoveRightLeftRotation) {
-  MoveToFrontTester mtf;
-
-  mtf.TestInsert(10);
-  mtf.TestInsert(15);
-  mtf.TestInsert(5);
-  mtf.TestInsert(8);
-  CheckTree(mtf, std::string(R"(
-10H3S4----5H2S2-----D2
-                    8H1S1-----D3
-          15H1S1----D2
-)")
-                     .substr(1));
-
-  mtf.TestRemove(15);
-
-  CheckTree(mtf, std::string(R"(
-8H2S3-----5H1S1-----D2
-          10H1S1----D2
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, MultipleOperations) {
-  MoveToFrontTester mtf;
-  std::vector<uint32_t> vals = {5, 11, 12, 16, 15, 6, 14, 2,
-                                7, 10, 4,  8,  9,  3, 1,  13};
-
-  for (uint32_t i : vals) {
-    mtf.TestInsert(i);
-  }
-
-  CheckTree(mtf, std::string(R"(
-11H5S16---5H4S10----3H3S4-----2H2S2-----1H1S1-----D5
-                              4H1S1-----D4
-                    7H3S5-----6H1S1-----D4
-                              9H2S3-----8H1S1-----D5
-                                        10H1S1----D5
-          15H3S5----13H2S3----12H1S1----D4
-                              14H1S1----D4
-                    16H1S1----D3
-)")
-                     .substr(1));
-
-  mtf.TestRemove(11);
-
-  CheckTree(mtf, std::string(R"(
-10H5S15---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5
-                              4H1S1-----D4
-                    7H3S4-----6H1S1-----D4
-                              9H2S2-----8H1S1-----D5
-          15H3S5----13H2S3----12H1S1----D4
-                              14H1S1----D4
-                    16H1S1----D3
-)")
-                     .substr(1));
-
-  mtf.TestInsert(11);
-
-  CheckTree(mtf, std::string(R"(
-10H5S16---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5
-                              4H1S1-----D4
-                    7H3S4-----6H1S1-----D4
-                              9H2S2-----8H1S1-----D5
-          13H3S6----12H2S2----11H1S1----D4
-                    15H2S3----14H1S1----D4
-                              16H1S1----D4
-)")
-                     .substr(1));
-
-  mtf.TestRemove(5);
-
-  CheckTree(mtf, std::string(R"(
-10H5S15---6H4S8-----3H3S4-----2H2S2-----1H1S1-----D5
-                              4H1S1-----D4
-                    8H2S3-----7H1S1-----D4
-                              9H1S1-----D4
-          13H3S6----12H2S2----11H1S1----D4
-                    15H2S3----14H1S1----D4
-                              16H1S1----D4
-)")
-                     .substr(1));
-
-  mtf.TestInsert(5);
-
-  CheckTree(mtf, std::string(R"(
-10H5S16---6H4S9-----3H3S5-----2H2S2-----1H1S1-----D5
-                              4H2S2-----D4
-                                        5H1S1-----D5
-                    8H2S3-----7H1S1-----D4
-                              9H1S1-----D4
-          13H3S6----12H2S2----11H1S1----D4
-                    15H2S3----14H1S1----D4
-                              16H1S1----D4
-)")
-                     .substr(1));
-
-  mtf.TestRemove(2);
-  mtf.TestRemove(1);
-  mtf.TestRemove(4);
-  mtf.TestRemove(3);
-  mtf.TestRemove(6);
-  mtf.TestRemove(5);
-  mtf.TestRemove(7);
-  mtf.TestRemove(9);
-
-  CheckTree(mtf, std::string(R"(
-13H4S8----10H3S4----8H1S1-----D3
-                    12H2S2----11H1S1----D4
-          15H2S3----14H1S1----D3
-                    16H1S1----D3
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, BiggerScaleTreeTest) {
-  MoveToFrontTester mtf;
-  std::set<uint32_t> all_vals;
-
-  const uint32_t kMagic1 = 2654435761;
-  const uint32_t kMagic2 = 10000;
-
-  for (uint32_t i = 1; i < 1000; ++i) {
-    const uint32_t val = (i * kMagic1) % kMagic2;
-    if (!all_vals.count(val)) {
-      mtf.TestInsert(val);
-      all_vals.insert(val);
-    }
-  }
-
-  for (uint32_t i = 1; i < 1000; ++i) {
-    const uint32_t val = (i * kMagic1) % kMagic2;
-    if (val % 2 == 0) {
-      mtf.TestRemove(val);
-      all_vals.erase(val);
-    }
-  }
-
-  for (uint32_t i = 1000; i < 2000; ++i) {
-    const uint32_t val = (i * kMagic1) % kMagic2;
-    if (!all_vals.count(val)) {
-      mtf.TestInsert(val);
-      all_vals.insert(val);
-    }
-  }
-
-  for (uint32_t i = 1; i < 2000; ++i) {
-    const uint32_t val = (i * kMagic1) % kMagic2;
-    if (val > 50) {
-      mtf.TestRemove(val);
-      all_vals.erase(val);
-    }
-  }
-
-  EXPECT_EQ(all_vals, std::set<uint32_t>({2, 4, 11, 13, 24, 33, 35, 37, 46}));
-
-  CheckTree(mtf, std::string(R"(
-33H4S9----11H3S5----2H2S2-----D3
-                              4H1S1-----D4
-                    13H2S2----D3
-                              24H1S1----D4
-          37H2S3----35H1S1----D3
-                    46H1S1----D3
-)")
-                     .substr(1));
-}
-
-TEST(MoveToFront, RankFromValue) {
-  MoveToFrontTester mtf;
-
-  uint32_t rank = 0;
-  EXPECT_FALSE(mtf.RankFromValue(1, &rank));
-
-  EXPECT_TRUE(mtf.Insert(1));
-  EXPECT_TRUE(mtf.Insert(2));
-  EXPECT_TRUE(mtf.Insert(3));
-  EXPECT_FALSE(mtf.Insert(2));
-  CheckTree(mtf,
-            std::string(R"(
-2H2S3T2-------1H1S1T1-------D2
-              3H1S1T3-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_FALSE(mtf.RankFromValue(4, &rank));
-
-  EXPECT_TRUE(mtf.RankFromValue(1, &rank));
-  EXPECT_EQ(3u, rank);
-
-  CheckTree(mtf,
-            std::string(R"(
-3H2S3T3-------2H1S1T2-------D2
-              1H1S1T4-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.RankFromValue(1, &rank));
-  EXPECT_EQ(1u, rank);
-
-  EXPECT_TRUE(mtf.RankFromValue(3, &rank));
-  EXPECT_EQ(2u, rank);
-
-  EXPECT_TRUE(mtf.RankFromValue(2, &rank));
-  EXPECT_EQ(3u, rank);
-
-  EXPECT_TRUE(mtf.Insert(40));
-
-  EXPECT_TRUE(mtf.RankFromValue(1, &rank));
-  EXPECT_EQ(4u, rank);
-
-  EXPECT_TRUE(mtf.Insert(50));
-
-  EXPECT_TRUE(mtf.RankFromValue(1, &rank));
-  EXPECT_EQ(2u, rank);
-
-  CheckTree(mtf,
-            std::string(R"(
-2H3S5T6-------3H1S1T5-------D2
-              50H2S3T9------40H1S1T7------D3
-                            1H1S1T10------D3
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.RankFromValue(50, &rank));
-  EXPECT_EQ(2u, rank);
-
-  EXPECT_EQ(5u, mtf.GetSize());
-  CheckTree(mtf,
-            std::string(R"(
-2H3S5T6-------3H1S1T5-------D2
-              1H2S3T10------40H1S1T7------D3
-                            50H1S1T11-----D3
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_FALSE(mtf.RankFromValue(0, &rank));
-  EXPECT_FALSE(mtf.RankFromValue(20, &rank));
-}
-
-TEST(MoveToFront, ValueFromRank) {
-  MoveToFrontTester mtf;
-
-  uint32_t value = 0;
-  EXPECT_FALSE(mtf.ValueFromRank(0, &value));
-  EXPECT_FALSE(mtf.ValueFromRank(1, &value));
-
-  EXPECT_TRUE(mtf.Insert(1));
-  EXPECT_EQ(1u, mtf.GetLastAccessedValue());
-  EXPECT_TRUE(mtf.Insert(2));
-  EXPECT_EQ(2u, mtf.GetLastAccessedValue());
-  EXPECT_TRUE(mtf.Insert(3));
-  EXPECT_EQ(3u, mtf.GetLastAccessedValue());
-
-  EXPECT_TRUE(mtf.ValueFromRank(3, &value));
-  EXPECT_EQ(1u, value);
-  EXPECT_EQ(1u, mtf.GetLastAccessedValue());
-
-  EXPECT_TRUE(mtf.ValueFromRank(1, &value));
-  EXPECT_EQ(1u, value);
-  EXPECT_EQ(1u, mtf.GetLastAccessedValue());
-
-  CheckTree(mtf,
-            std::string(R"(
-3H2S3T3-------2H1S1T2-------D2
-              1H1S1T4-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.ValueFromRank(2, &value));
-  EXPECT_EQ(3u, value);
-
-  EXPECT_EQ(3u, mtf.GetSize());
-
-  CheckTree(mtf,
-            std::string(R"(
-1H2S3T4-------2H1S1T2-------D2
-              3H1S1T5-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.ValueFromRank(3, &value));
-  EXPECT_EQ(2u, value);
-
-  CheckTree(mtf,
-            std::string(R"(
-3H2S3T5-------1H1S1T4-------D2
-              2H1S1T6-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.Insert(10));
-  CheckTree(mtf,
-            std::string(R"(
-3H3S4T5-------1H1S1T4-------D2
-              2H2S2T6-------D2
-                            10H1S1T7------D3
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.ValueFromRank(1, &value));
-  EXPECT_EQ(10u, value);
-}
-
-TEST(MoveToFront, Remove) {
-  MoveToFrontTester mtf;
-
-  EXPECT_FALSE(mtf.Remove(1));
-  EXPECT_EQ(0u, mtf.GetTotalNodeCount());
-
-  EXPECT_TRUE(mtf.Insert(1));
-  EXPECT_TRUE(mtf.Insert(2));
-  EXPECT_TRUE(mtf.Insert(3));
-
-  CheckTree(mtf,
-            std::string(R"(
-2H2S3T2-------1H1S1T1-------D2
-              3H1S1T3-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_EQ(1u, mtf.GetNodeHandle(1));
-  EXPECT_EQ(3u, mtf.GetTotalNodeCount());
-  EXPECT_TRUE(mtf.Remove(1));
-  EXPECT_EQ(3u, mtf.GetTotalNodeCount());
-
-  CheckTree(mtf,
-            std::string(R"(
-2H2S2T2-------D1
-              3H1S1T3-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  uint32_t value = 0;
-  EXPECT_TRUE(mtf.ValueFromRank(2, &value));
-  EXPECT_EQ(2u, value);
-
-  CheckTree(mtf,
-            std::string(R"(
-3H2S2T3-------D1
-              2H1S1T4-------D2
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  EXPECT_TRUE(mtf.Insert(1));
-  EXPECT_EQ(1u, mtf.GetNodeHandle(1));
-  EXPECT_EQ(3u, mtf.GetTotalNodeCount());
-}
-
-TEST(MoveToFront, LargerScale) {
-  MoveToFrontTester mtf;
-  uint32_t value = 0;
-  uint32_t rank = 0;
-
-  for (uint32_t i = 1; i < 1000; ++i) {
-    ASSERT_TRUE(mtf.Insert(i));
-    ASSERT_EQ(i, mtf.GetSize());
-
-    ASSERT_TRUE(mtf.RankFromValue(i, &rank));
-    ASSERT_EQ(1u, rank);
-
-    ASSERT_TRUE(mtf.ValueFromRank(1, &value));
-    ASSERT_EQ(i, value);
-  }
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(1u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(2u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(3u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(4u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(5u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(999, &value));
-  ASSERT_EQ(6u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(101, &value));
-  ASSERT_EQ(905u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(101, &value));
-  ASSERT_EQ(906u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(101, &value));
-  ASSERT_EQ(907u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(201, &value));
-  ASSERT_EQ(805u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(201, &value));
-  ASSERT_EQ(806u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(201, &value));
-  ASSERT_EQ(807u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(301, &value));
-  ASSERT_EQ(705u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(301, &value));
-  ASSERT_EQ(706u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(301, &value));
-  ASSERT_EQ(707u, value);
-
-  ASSERT_TRUE(mtf.RankFromValue(605, &rank));
-  ASSERT_EQ(401u, rank);
-
-  ASSERT_TRUE(mtf.RankFromValue(606, &rank));
-  ASSERT_EQ(401u, rank);
-
-  ASSERT_TRUE(mtf.RankFromValue(607, &rank));
-  ASSERT_EQ(401u, rank);
-
-  ASSERT_TRUE(mtf.ValueFromRank(1, &value));
-  ASSERT_EQ(607u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(2, &value));
-  ASSERT_EQ(606u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(3, &value));
-  ASSERT_EQ(605u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(4, &value));
-  ASSERT_EQ(707u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(5, &value));
-  ASSERT_EQ(706u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(6, &value));
-  ASSERT_EQ(705u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(7, &value));
-  ASSERT_EQ(807u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(8, &value));
-  ASSERT_EQ(806u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(9, &value));
-  ASSERT_EQ(805u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(10, &value));
-  ASSERT_EQ(907u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(11, &value));
-  ASSERT_EQ(906u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(12, &value));
-  ASSERT_EQ(905u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(13, &value));
-  ASSERT_EQ(6u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(14, &value));
-  ASSERT_EQ(5u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(15, &value));
-  ASSERT_EQ(4u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(16, &value));
-  ASSERT_EQ(3u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(17, &value));
-  ASSERT_EQ(2u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(18, &value));
-  ASSERT_EQ(1u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(19, &value));
-  ASSERT_EQ(999u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(20, &value));
-  ASSERT_EQ(998u, value);
-
-  ASSERT_TRUE(mtf.ValueFromRank(21, &value));
-  ASSERT_EQ(997u, value);
-
-  ASSERT_TRUE(mtf.RankFromValue(997, &rank));
-  ASSERT_EQ(1u, rank);
-
-  ASSERT_TRUE(mtf.RankFromValue(998, &rank));
-  ASSERT_EQ(2u, rank);
-
-  ASSERT_TRUE(mtf.RankFromValue(996, &rank));
-  ASSERT_EQ(22u, rank);
-
-  ASSERT_TRUE(mtf.Remove(995));
-
-  ASSERT_TRUE(mtf.RankFromValue(994, &rank));
-  ASSERT_EQ(23u, rank);
-
-  for (uint32_t i = 10; i < 1000; ++i) {
-    if (i != 995) {
-      ASSERT_TRUE(mtf.Remove(i));
-    } else {
-      ASSERT_FALSE(mtf.Remove(i));
-    }
-  }
-
-  CheckTree(mtf,
-            std::string(R"(
-6H4S9T1029----8H2S3T8-------7H1S1T7-------D3
-                            9H1S1T9-------D3
-              2H3S5T1033----4H2S3T1031----5H1S1T1030----D4
-                                          3H1S1T1032----D4
-                            1H1S1T1034----D3
-)")
-                .substr(1),
-            /* print_timestamp = */ true);
-
-  ASSERT_TRUE(mtf.Insert(1000));
-  ASSERT_TRUE(mtf.ValueFromRank(1, &value));
-  ASSERT_EQ(1000u, value);
-}
-
-}  // namespace
-}  // namespace comp
-}  // namespace spvtools
diff --git a/test/name_mapper_test.cpp b/test/name_mapper_test.cpp
index 9a9ee8a..00fbeed 100644
--- a/test/name_mapper_test.cpp
+++ b/test/name_mapper_test.cpp
@@ -54,31 +54,31 @@
       << " for id " << GetParam().id;
 }
 
-INSTANTIATE_TEST_CASE_P(ScalarType, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeVoid", 1, "void"},
-                            {"%1 = OpTypeBool", 1, "bool"},
-                            {"%1 = OpTypeInt 8 0", 1, "uchar"},
-                            {"%1 = OpTypeInt 8 1", 1, "char"},
-                            {"%1 = OpTypeInt 16 0", 1, "ushort"},
-                            {"%1 = OpTypeInt 16 1", 1, "short"},
-                            {"%1 = OpTypeInt 32 0", 1, "uint"},
-                            {"%1 = OpTypeInt 32 1", 1, "int"},
-                            {"%1 = OpTypeInt 64 0", 1, "ulong"},
-                            {"%1 = OpTypeInt 64 1", 1, "long"},
-                            {"%1 = OpTypeInt 1 0", 1, "u1"},
-                            {"%1 = OpTypeInt 1 1", 1, "i1"},
-                            {"%1 = OpTypeInt 33 0", 1, "u33"},
-                            {"%1 = OpTypeInt 33 1", 1, "i33"},
+INSTANTIATE_TEST_SUITE_P(ScalarType, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeVoid", 1, "void"},
+                             {"%1 = OpTypeBool", 1, "bool"},
+                             {"%1 = OpTypeInt 8 0", 1, "uchar"},
+                             {"%1 = OpTypeInt 8 1", 1, "char"},
+                             {"%1 = OpTypeInt 16 0", 1, "ushort"},
+                             {"%1 = OpTypeInt 16 1", 1, "short"},
+                             {"%1 = OpTypeInt 32 0", 1, "uint"},
+                             {"%1 = OpTypeInt 32 1", 1, "int"},
+                             {"%1 = OpTypeInt 64 0", 1, "ulong"},
+                             {"%1 = OpTypeInt 64 1", 1, "long"},
+                             {"%1 = OpTypeInt 1 0", 1, "u1"},
+                             {"%1 = OpTypeInt 1 1", 1, "i1"},
+                             {"%1 = OpTypeInt 33 0", 1, "u33"},
+                             {"%1 = OpTypeInt 33 1", 1, "i33"},
 
-                            {"%1 = OpTypeFloat 16", 1, "half"},
-                            {"%1 = OpTypeFloat 32", 1, "float"},
-                            {"%1 = OpTypeFloat 64", 1, "double"},
-                            {"%1 = OpTypeFloat 10", 1, "fp10"},
-                            {"%1 = OpTypeFloat 55", 1, "fp55"},
-                        }), );
+                             {"%1 = OpTypeFloat 16", 1, "half"},
+                             {"%1 = OpTypeFloat 32", 1, "float"},
+                             {"%1 = OpTypeFloat 64", 1, "double"},
+                             {"%1 = OpTypeFloat 10", 1, "fp10"},
+                             {"%1 = OpTypeFloat 55", 1, "fp55"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorType, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool %2 = OpTypeVector %1 1", 2, "v1bool"},
@@ -97,9 +97,9 @@
         // OpName overrides the element name.
         {"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2,
          "v2time"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MatrixType, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 2", 3,
@@ -114,9 +114,9 @@
         {"OpName %2 \"lat_long\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 "
          "= OpTypeMatrix %2 4",
          3, "mat4lat_long"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpName, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"OpName %1 \"abcdefg\"", 1, "abcdefg"},
@@ -146,38 +146,38 @@
         // OpName can override other inferences.  We assume valid instruction
         // ordering, where OpName precedes type definitions.
         {"OpName %1 \"myfloat\" %1 = OpTypeFloat 32", 1, "myfloat"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     UniquenessHeuristic, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 1, "void"},
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 2, "void_0"},
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 3, "void_1"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(Arrays, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 "
-                             "%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2",
-                             3, "_arr_float_FortyTwo"},
-                            {"%1 = OpTypeInt 32 0 "
-                             "%2 = OpTypeRuntimeArray %1",
-                             2, "_runtimearr_uint"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Arrays, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 "
+                              "%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2",
+                              3, "_arr_float_FortyTwo"},
+                             {"%1 = OpTypeInt 32 0 "
+                              "%2 = OpTypeRuntimeArray %1",
+                              2, "_runtimearr_uint"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(Structs, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeBool "
-                             "%2 = OpTypeStruct %1 %1 %1",
-                             2, "_struct_2"},
-                            {"%1 = OpTypeBool "
-                             "%2 = OpTypeStruct %1 %1 %1 "
-                             "%3 = OpTypeStruct %2 %2",
-                             3, "_struct_3"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Structs, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeBool "
+                              "%2 = OpTypeStruct %1 %1 %1",
+                              2, "_struct_2"},
+                             {"%1 = OpTypeBool "
+                              "%2 = OpTypeStruct %1 %1 %1 "
+                              "%3 = OpTypeStruct %2 %2",
+                              3, "_struct_3"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Pointer, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeFloat 32 %2 = OpTypePointer Workgroup %1", 2,
@@ -189,22 +189,22 @@
         {"%1 = OpTypeBool OpTypeForwardPointer %2 Private %2 = OpTypePointer "
          "Private %1",
          2, "_ptr_Private_bool"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(ExoticTypes, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeEvent", 1, "Event"},
-                            {"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"},
-                            {"%1 = OpTypeReserveId", 1, "ReserveId"},
-                            {"%1 = OpTypeQueue", 1, "Queue"},
-                            {"%1 = OpTypeOpaque \"hello world!\"", 1,
-                             "Opaque_hello_world_"},
-                            {"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"},
-                            {"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"},
-                            {"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"},
-                            {"%1 = OpTypePipeStorage", 1, "PipeStorage"},
-                            {"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ExoticTypes, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeEvent", 1, "Event"},
+                             {"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"},
+                             {"%1 = OpTypeReserveId", 1, "ReserveId"},
+                             {"%1 = OpTypeQueue", 1, "Queue"},
+                             {"%1 = OpTypeOpaque \"hello world!\"", 1,
+                              "Opaque_hello_world_"},
+                             {"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"},
+                             {"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"},
+                             {"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"},
+                             {"%1 = OpTypePipeStorage", 1, "PipeStorage"},
+                             {"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"},
+                         }));
 
 // Makes a test case for a BuiltIn variable declaration.
 NameIdCase BuiltInCase(std::string assembly_name, std::string expected) {
@@ -226,7 +226,7 @@
   return BuiltInCase(assembly_name, std::string("gl_") + assembly_name);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BuiltIns, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         BuiltInGLCase("Position"),
@@ -275,15 +275,15 @@
         BuiltInCase("SubgroupGtMaskKHR"),
         BuiltInCase("SubgroupLeMaskKHR"),
         BuiltInCase("SubgroupLtMaskKHR"),
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(DebugNameOverridesBuiltin, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"OpName %1 \"foo\" OpDecorate %1 BuiltIn WorkDim "
-                             "%1 = OpVariable %2 Input",
-                             1, "foo"}}), );
+INSTANTIATE_TEST_SUITE_P(DebugNameOverridesBuiltin, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"OpName %1 \"foo\" OpDecorate %1 BuiltIn WorkDim "
+                              "%1 = OpVariable %2 Input",
+                              1, "foo"}}));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleIntegralConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 0", 2, "uint_0"},
@@ -301,9 +301,9 @@
         {"%1 = OpTypeInt 33 0 %2 = OpConstant %1 0", 2, "u33_0"},
         {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 10", 2, "i33_10"},
         {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 -19", 2, "i33_n19"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleFloatConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ff4p+16", 2,
@@ -334,14 +334,14 @@
          "double_0x1p_1024"},  // Inf
         {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024", 2,
          "double_n0x1p_1024"},  // -Inf
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BooleanConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool\n%2 = OpConstantTrue %1", 2, "true"},
         {"%1 = OpTypeBool\n%2 = OpConstantFalse %1", 2, "false"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/named_id_test.cpp b/test/named_id_test.cpp
index 4ba54ad..01f09be 100644
--- a/test/named_id_test.cpp
+++ b/test/named_id_test.cpp
@@ -65,7 +65,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidAndInvalidIds, IdValidityTest,
     ::testing::ValuesIn(std::vector<IdCheckCase>(
         {{"%1", true},          {"%2abc", true},   {"%3Def", true},
@@ -81,7 +81,7 @@
          {"%foo_@_bar", false}, {"%", false},
 
          {"5", false},          {"32", false},     {"foo", false},
-         {"a%bar", false}})), );
+         {"a%bar", false}})));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/opcode_require_capabilities_test.cpp b/test/opcode_require_capabilities_test.cpp
index 32bf1dc..07e86f8 100644
--- a/test/opcode_require_capabilities_test.cpp
+++ b/test/opcode_require_capabilities_test.cpp
@@ -42,7 +42,7 @@
       ElementsIn(CapabilitySet(entry->numCapabilities, entry->capabilities)));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TableRowTest, OpcodeTableCapabilitiesTest,
     // Spot-check a few opcodes.
     ::testing::Values(
@@ -72,7 +72,7 @@
                                    CapabilitySet{SpvCapabilityNamedBarrier}},
         ExpectedOpCodeCapabilities{
             SpvOpGetKernelMaxNumSubgroups,
-            CapabilitySet{SpvCapabilitySubgroupDispatch}}), );
+            CapabilitySet{SpvCapabilitySubgroupDispatch}}));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/opcode_table_get_test.cpp b/test/opcode_table_get_test.cpp
index 6f80ad7..5ebd6c1 100644
--- a/test/opcode_table_get_test.cpp
+++ b/test/opcode_table_get_test.cpp
@@ -32,8 +32,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
-                        ValuesIn(spvtest::AllTargetEnvironments()));
+INSTANTIATE_TEST_SUITE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
+                         ValuesIn(spvtest::AllTargetEnvironments()));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index e58bc9d..5b06527 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -91,7 +91,7 @@
   }
 
 // See SPIR-V Section 3.3 Execution Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -104,30 +104,30 @@
                 CASE1(EXECUTION_MODEL, ExecutionModelFragment, Shader),
                 CASE1(EXECUTION_MODEL, ExecutionModelGLCompute, Shader),
                 CASE1(EXECUTION_MODEL, ExecutionModelKernel, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.4 Addressing Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AddressingModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(ADDRESSING_MODEL, AddressingModelLogical),
                 CASE1(ADDRESSING_MODEL, AddressingModelPhysical32, Addresses),
                 CASE1(ADDRESSING_MODEL, AddressingModelPhysical64, Addresses),
-            })), );
+            })));
 
 // See SPIR-V Section 3.5 Memory Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(MEMORY_MODEL, MemoryModelSimple, Shader),
                 CASE1(MEMORY_MODEL, MemoryModelGLSL450, Shader),
                 CASE1(MEMORY_MODEL, MemoryModelOpenCL, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.6 Execution Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionMode, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -169,9 +169,9 @@
             CASE1(EXECUTION_MODE, ExecutionModeOutputTriangleStrip, Geometry),
             CASE1(EXECUTION_MODE, ExecutionModeVecTypeHint, Kernel),
             CASE1(EXECUTION_MODE, ExecutionModeContractionOff, Kernel),
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModeV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -180,10 +180,10 @@
                 CASE1(EXECUTION_MODE, ExecutionModeSubgroupSize,
                       SubgroupDispatch),
                 CASE1(EXECUTION_MODE, ExecutionModeSubgroupsPerWorkgroup,
-                      SubgroupDispatch)})), );
+                      SubgroupDispatch)})));
 
 // See SPIR-V Section 3.7 Storage Class
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StorageClass, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -199,10 +199,10 @@
                 CASE1(STORAGE_CLASS, StorageClassPushConstant, Shader),
                 CASE1(STORAGE_CLASS, StorageClassAtomicCounter, AtomicStorage),
                 CASE0(STORAGE_CLASS, StorageClassImage),
-            })), );
+            })));
 
 // See SPIR-V Section 3.8 Dim
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Dim, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -213,10 +213,10 @@
                 CASE2(DIMENSIONALITY, DimRect, SampledRect, ImageRect),
                 CASE2(DIMENSIONALITY, DimBuffer, SampledBuffer, ImageBuffer),
                 CASE1(DIMENSIONALITY, DimSubpassData, InputAttachment),
-            })), );
+            })));
 
 // See SPIR-V Section 3.9 Sampler Addressing Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplerAddressingMode, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -228,19 +228,19 @@
             CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeat, Kernel),
             CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeatMirrored,
                   Kernel),
-        })), );
+        })));
 
 // See SPIR-V Section 3.10 Sampler Filter Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplerFilterMode, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeNearest, Kernel),
                 CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeLinear, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.11 Image Format
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageFormat, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -286,10 +286,10 @@
         CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16ui, StorageImageExtendedFormats),
         CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8ui, StorageImageExtendedFormats),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.12 Image Channel Order
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageChannelOrder, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -314,10 +314,10 @@
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersRGBA, Kernel),
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersBGRA, Kernel),
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderABGR, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.13 Image Channel Data Type
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageChannelDataType, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -340,10 +340,10 @@
                 CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt24, Kernel),
                 CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt101010_2, Kernel),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.14 Image Operands
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageOperands, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -358,10 +358,10 @@
                 CASE0(OPTIONAL_IMAGE, ImageOperandsSampleMask),
                 CASE1(OPTIONAL_IMAGE, ImageOperandsMinLodMask, MinLod),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.15 FP Fast Math Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FPFastMathMode, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -371,29 +371,29 @@
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeNSZMask, Kernel),
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeAllowRecipMask, Kernel),
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeFastMask, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.17 Linkage Type
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LinkageType, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(LINKAGE_TYPE, LinkageTypeExport, Linkage),
                 CASE1(LINKAGE_TYPE, LinkageTypeImport, Linkage),
-            })), );
+            })));
 
 // See SPIR-V Section 3.18 Access Qualifier
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AccessQualifier, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(ACCESS_QUALIFIER, AccessQualifierReadOnly, Kernel),
                 CASE1(ACCESS_QUALIFIER, AccessQualifierWriteOnly, Kernel),
                 CASE1(ACCESS_QUALIFIER, AccessQualifierReadWrite, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.19 Function Parameter Attribute
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionParameterAttribute, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -407,10 +407,10 @@
                 CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoWrite, Kernel),
                 CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoReadWrite, Kernel),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.20 Decoration
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Decoration, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -460,25 +460,25 @@
                 CASE1(DECORATION, DecorationInputAttachmentIndex,
                       InputAttachment),
                 CASE1(DECORATION, DecorationAlignment, Kernel),
-            })), );
+            })));
 
 #if 0
 // SpecId has different requirements in v1.0 and v1.1:
-INSTANTIATE_TEST_CASE_P(DecorationSpecIdV10, EnumCapabilityTest,
+INSTANTIATE_TEST_SUITE_P(DecorationSpecIdV10, EnumCapabilityTest,
                         Combine(Values(SPV_ENV_UNIVERSAL_1_0),
                                 ValuesIn(std::vector<EnumCapabilityCase>{CASE1(
-                                    DECORATION, DecorationSpecId, Shader)})), );
+                                    DECORATION, DecorationSpecId, Shader)})));
 #endif
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE2(DECORATION, DecorationSpecId, Shader, Kernel),
-                CASE1(DECORATION, DecorationMaxByteOffset, Addresses)})), );
+                CASE1(DECORATION, DecorationMaxByteOffset, Addresses)})));
 
 // See SPIR-V Section 3.21 BuiltIn
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BuiltIn, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -530,38 +530,38 @@
             CASE1(BUILT_IN, BuiltInVertexIndex, Shader),
             CASE1(BUILT_IN, BuiltInInstanceIndex, Shader),
             // clang-format on
-        })), );
+        })));
 
 // See SPIR-V Section 3.22 Selection Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SelectionControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(SELECTION_CONTROL, SelectionControlMaskNone),
                 CASE0(SELECTION_CONTROL, SelectionControlFlattenMask),
                 CASE0(SELECTION_CONTROL, SelectionControlDontFlattenMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.23 Loop Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(LOOP_CONTROL, LoopControlMaskNone),
                 CASE0(LOOP_CONTROL, LoopControlUnrollMask),
                 CASE0(LOOP_CONTROL, LoopControlDontUnrollMask),
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControlV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(LOOP_CONTROL, LoopControlDependencyInfiniteMask),
                 CASE0(LOOP_CONTROL, LoopControlDependencyLengthMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.24 Function Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -570,10 +570,10 @@
                 CASE0(FUNCTION_CONTROL, FunctionControlDontInlineMask),
                 CASE0(FUNCTION_CONTROL, FunctionControlPureMask),
                 CASE0(FUNCTION_CONTROL, FunctionControlConstMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.25 Memory Semantics <id>
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemorySemantics, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -592,10 +592,10 @@
             CASE1(MEMORY_SEMANTICS_ID, MemorySemanticsAtomicCounterMemoryMask,
                   AtomicStorage),  // Bug 15234
             CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsImageMemoryMask),
-        })), );
+        })));
 
 // See SPIR-V Section 3.26 Memory Access
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryAccess, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -603,10 +603,10 @@
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessVolatileMask),
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessAlignedMask),
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessNontemporalMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.27 Scope <id>
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Scope, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                    SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -617,10 +617,10 @@
                 CASE0(SCOPE_ID, ScopeSubgroup),
                 CASE0(SCOPE_ID, ScopeInvocation),
                 CASE1(SCOPE_ID, ScopeQueueFamilyKHR, VulkanMemoryModelKHR),
-            })), );
+            })));
 
 // See SPIR-V Section 3.28 Group Operation
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupOperation, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -630,10 +630,10 @@
                       GroupNonUniformArithmetic, GroupNonUniformBallot),
                 CASE3(GROUP_OPERATION, GroupOperationExclusiveScan, Kernel,
                       GroupNonUniformArithmetic, GroupNonUniformBallot),
-            })), );
+            })));
 
 // See SPIR-V Section 3.29 Kernel Enqueue Flags
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KernelEnqueueFlags, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -641,20 +641,20 @@
                 CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitKernel, Kernel),
                 CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitWorkGroup,
                       Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.30 Kernel Profiling Info
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KernelProfilingInfo, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(KERNEL_PROFILING_INFO, KernelProfilingInfoMaskNone),
                 CASE1(KERNEL_PROFILING_INFO, KernelProfilingInfoCmdExecTimeMask,
                       Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.31 Capability
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CapabilityDependsOn, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -717,16 +717,16 @@
             CASE1(CAPABILITY, CapabilityStorageImageWriteWithoutFormat, Shader),
             CASE1(CAPABILITY, CapabilityMultiViewport, Geometry),
             // clang-format on
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CapabilityDependsOnV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(CAPABILITY, CapabilitySubgroupDispatch, DeviceEnqueue),
                 CASE1(CAPABILITY, CapabilityNamedBarrier, Kernel),
                 CASE1(CAPABILITY, CapabilityPipeStorage, Pipes),
-            })), );
+            })));
 
 #undef CASE0
 #undef CASE1
diff --git a/test/operand_pattern_test.cpp b/test/operand_pattern_test.cpp
index d2d92a0..1caf008 100644
--- a/test/operand_pattern_test.cpp
+++ b/test/operand_pattern_test.cpp
@@ -84,7 +84,7 @@
 #define PREFIX1                                                         \
   SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE, \
       SPV_OPERAND_TYPE_ID
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OperandPattern, MaskExpansionTest,
     ::testing::ValuesIn(std::vector<MaskExpansionCase>{
         // No bits means no change.
@@ -111,7 +111,7 @@
          SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask,
          {PREFIX1},
          {PREFIX1, SPV_OPERAND_TYPE_LITERAL_INTEGER}},
-    }), );
+    }));
 #undef PREFIX0
 #undef PREFIX1
 
@@ -137,9 +137,9 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(MatchableOperandExpansion,
-                        MatchableOperandExpansionTest,
-                        ::testing::ValuesIn(allOperandTypes()), );
+INSTANTIATE_TEST_SUITE_P(MatchableOperandExpansion,
+                         MatchableOperandExpansionTest,
+                         ::testing::ValuesIn(allOperandTypes()));
 
 using VariableOperandExpansionTest =
     ::testing::TestWithParam<spv_operand_type_t>;
@@ -157,9 +157,9 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(NonMatchableOperandExpansion,
-                        VariableOperandExpansionTest,
-                        ::testing::ValuesIn(allOperandTypes()), );
+INSTANTIATE_TEST_SUITE_P(NonMatchableOperandExpansion,
+                         VariableOperandExpansionTest,
+                         ::testing::ValuesIn(allOperandTypes()));
 
 TEST(AlternatePatternFollowingImmediate, Empty) {
   EXPECT_THAT(spvAlternatePatternFollowingImmediate({}),
diff --git a/test/operand_test.cpp b/test/operand_test.cpp
index 08522c3..4e2c321 100644
--- a/test/operand_test.cpp
+++ b/test/operand_test.cpp
@@ -33,10 +33,10 @@
   ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOperandTableGet(nullptr, GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(OperandTableGet, GetTargetTest,
-                        ValuesIn(std::vector<spv_target_env>{
-                            SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
-                            SPV_ENV_VULKAN_1_0}), );
+INSTANTIATE_TEST_SUITE_P(OperandTableGet, GetTargetTest,
+                         ValuesIn(std::vector<spv_target_env>{
+                             SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+                             SPV_ENV_VULKAN_1_0}));
 
 TEST(OperandString, AllAreDefinedExceptVariable) {
   // None has no string, so don't test it.
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 6315385..246c116 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -21,25 +21,29 @@
        block_merge_test.cpp
        ccp_test.cpp
        cfg_cleanup_test.cpp
+       cfg_test.cpp
        code_sink_test.cpp
        combine_access_chains_test.cpp
-       common_uniform_elim_test.cpp
        compact_ids_test.cpp
        constant_manager_test.cpp
        copy_prop_array_test.cpp
        dead_branch_elim_test.cpp
        dead_insert_elim_test.cpp
        dead_variable_elim_test.cpp
+       decompose_initialized_variables_test.cpp
        decoration_manager_test.cpp
        def_use_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_functions_test.cpp
+       eliminate_dead_member_test.cpp
        feature_manager_test.cpp
+       fix_storage_class_test.cpp
        flatten_decoration_test.cpp
        fold_spec_const_op_composite_test.cpp
        fold_test.cpp
        freeze_spec_const_test.cpp
        function_test.cpp
+       generate_webgpu_initializers_test.cpp
        if_conversion_test.cpp
        inline_opaque_test.cpp
        inline_test.cpp
@@ -51,6 +55,7 @@
        ir_context_test.cpp
        ir_loader_test.cpp
        iterator_test.cpp
+       legalize_vector_shuffle_test.cpp
        line_debug_info_test.cpp
        local_access_chain_convert_test.cpp
        local_redundancy_elimination_test.cpp
@@ -75,7 +80,9 @@
        scalar_replacement_test.cpp
        set_spec_const_default_value_test.cpp
        simplification_test.cpp
+       split_invalid_unreachable_test.cpp
        strength_reduction_test.cpp
+       strip_atomic_counter_memory_test.cpp
        strip_debug_info_test.cpp
        strip_reflect_info_test.cpp
        struct_cfg_analysis_test.cpp
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index e58a76f..b4ab10d 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -3811,7 +3811,7 @@
 %6 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_ptr_Private_float = OpTypePointer Private %float
-%initializer = OpVariable %_ptr_Private_float Private
+%initializer = OpConstant %float 0
 %live = OpVariable %_ptr_Private_float Private %initializer
 %_ptr_Output_float = OpTypePointer Output %float
 %output = OpVariable %_ptr_Output_float Output
@@ -4254,7 +4254,7 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(assembly_with_dead_const, false);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4346,7 +4346,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4473,7 +4473,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StructTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4644,7 +4644,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeSpecConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4697,7 +4697,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeSpecConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4819,7 +4819,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SpecConstantOp, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -5001,7 +5001,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -5151,7 +5151,6 @@
 TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Restrict
-; CHECK: OpDecorate [[grp]] Aliased
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
 ; CHECK: [[output]] = OpVariable {{%\w+}} Output
@@ -5161,7 +5160,6 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Restrict
-OpDecorate %1 Aliased
 %1 = OpDecorationGroup
 OpGroupDecorate %1 %var %output
 %void = OpTypeVoid
@@ -5185,7 +5183,6 @@
 TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroupDifferentGroupDecorate) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Restrict
-; CHECK: OpDecorate [[grp]] Aliased
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
 ; CHECK-NOT: OpGroupDecorate
@@ -5196,7 +5193,6 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Restrict
-OpDecorate %1 Aliased
 %1 = OpDecorationGroup
 OpGroupDecorate %1 %output
 OpGroupDecorate %1 %var
@@ -6214,6 +6210,412 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
 }
+
+TEST_F(AggressiveDCETest, DeadInfiniteLoop) {
+  const std::string test = R"(
+; CHECK: OpSwitch {{%\w+}} {{%\w+}} {{\w+}} {{%\w+}} {{\w+}} [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypeVector %8 3
+         %10 = OpTypeFunction %9
+         %11 = OpConstant %8 1
+         %12 = OpConstantComposite %9 %11 %11 %11
+         %13 = OpTypeInt 32 1
+         %32 = OpUndef %13
+          %2 = OpFunction %6 None %7
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpFunctionCall %9 %39
+               OpSelectionMerge %40 None
+               OpSwitch %32 %40 14 %41 58 %42
+         %42 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %44 %45 None
+               OpBranch %45
+         %45 = OpLabel
+               OpBranch %43
+         %44 = OpLabel
+               OpUnreachable
+         %41 = OpLabel
+               OpBranch %36
+         %40 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %39 = OpFunction %9 None %10
+         %46 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInfiniteLoopReturnValue) {
+  const std::string test = R"(
+; CHECK: [[vec3:%\w+]] = OpTypeVector
+; CHECK: [[undef:%\w+]] = OpUndef [[vec3]]
+; CHECK: OpSwitch {{%\w+}} {{%\w+}} {{\w+}} {{%\w+}} {{\w+}} [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpReturnValue [[undef]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypeVector %8 3
+         %10 = OpTypeFunction %9
+         %11 = OpConstant %8 1
+         %12 = OpConstantComposite %9 %11 %11 %11
+         %13 = OpTypeInt 32 1
+         %32 = OpUndef %13
+          %2 = OpFunction %6 None %7
+      %entry = OpLabel
+       %call = OpFunctionCall %9 %func
+               OpReturn
+               OpFunctionEnd
+       %func = OpFunction %9 None %10
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpFunctionCall %9 %39
+               OpSelectionMerge %40 None
+               OpSwitch %32 %40 14 %41 58 %42
+         %42 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %44 %45 None
+               OpBranch %45
+         %45 = OpLabel
+               OpBranch %43
+         %44 = OpLabel
+               OpUnreachable
+         %41 = OpLabel
+               OpBranch %36
+         %40 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+         %39 = OpFunction %9 None %10
+         %46 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+
+TEST_F(AggressiveDCETest, TestVariablePointer) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability VariablePointers
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "main"
+OpExecutionMode %2 LocalSize 1 1 1
+OpSource GLSL 450
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_struct_3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+OpDecorate %_arr_int_int_128 ArrayStride 4
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+%_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%2 = OpFunction %void None %8
+%16 = OpLabel
+%17 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %_ptr_StorageBuffer_int %17 %16 %20 %21
+OpLoopMerge %22 %21 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+OpStore %19 %int_0
+OpBranch %21
+%21 = OpLabel
+%20 = OpPtrAccessChain %_ptr_StorageBuffer_int %19 %int_1
+OpBranch %18
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<AggressiveDCEPass>(before, before, true, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInputInterfaceV13) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
+; CHECK: [[var]] = OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_input_int = OpTypePointer Input %int
+%dead = OpVariable %ptr_input_int Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_3);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInputInterfaceV14) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
+; CHECK: [[var]] = OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_input_int = OpTypePointer Input %int
+%dead = OpVariable %ptr_input_int Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInterfaceV14) {
+  const std::string spirv = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %main "main" %
+; CHECK: OpEntryPoint GLCompute %main "main"
+; CHECK-NOT: OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_private_int = OpTypePointer Private %int
+%dead = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInterfacesV14) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" %live1 %live2
+; CHECK-NOT: %dead
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %live1 %dead1 %dead2 %live2
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+OpName %live1 "live1"
+OpName %live2 "live2"
+OpName %dead1 "dead1"
+OpName %dead2 "dead2"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%live1 = OpVariable %ptr_ssbo_int StorageBuffer
+%live2 = OpVariable %ptr_ssbo_int StorageBuffer
+%dead1 = OpVariable %ptr_ssbo_int StorageBuffer
+%dead2 = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpStore %live1 %int0
+OpStore %live2 %int0
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, PreserveBindings) {
+  const std::string spirv = R"(
+; CHECK: OpDecorate %unusedSampler DescriptorSet 0
+; CHECK: OpDecorate %unusedSampler Binding 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %unusedSampler "unusedSampler"
+OpDecorate %unusedSampler DescriptorSet 0
+OpDecorate %unusedSampler Binding 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%8 = OpTypeSampledImage %7
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+%unusedSampler = OpVariable %_ptr_UniformConstant_8 UniformConstant
+%main = OpFunction %void None %5
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+
+  OptimizerOptions()->preserve_bindings_ = true;
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, PreserveSpecConstants) {
+  const std::string spirv = R"(
+; CHECK: OpName %specConstant "specConstant"
+; CHECK: %specConstant = OpSpecConstant %int 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %specConstant "specConstant"
+OpDecorate %specConstant SpecId 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%specConstant = OpSpecConstant %int 0
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+
+  OptimizerOptions()->preserve_spec_constants_ = true;
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, LiveDecorateId) {
+  const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main" %2
+OpExecutionMode %1 LocalSize 8 1 1
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
+OpDecorateId %3 UniformId %uint_2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_2 = OpConstant %uint 2
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%2 = OpVariable %_ptr_StorageBuffer_uint StorageBuffer
+%8 = OpTypeFunction %void
+%1 = OpFunction %void None %8
+%9 = OpLabel
+%3 = OpLoad %uint %2
+OpStore %2 %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  OptimizerOptions()->preserve_spec_constants_ = true;
+  SinglePassRunAndCheck<AggressiveDCEPass>(spirv, spirv, true);
+}
+
+TEST_F(AggressiveDCETest, LiveDecorateIdOnGroup) {
+  const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main" %2
+OpExecutionMode %1 LocalSize 8 1 1
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
+OpDecorateId %3 UniformId %uint_2
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %5
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_2 = OpConstant %uint 2
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%2 = OpVariable %_ptr_StorageBuffer_uint StorageBuffer
+%9 = OpTypeFunction %void
+%1 = OpFunction %void None %9
+%10 = OpLabel
+%5 = OpLoad %uint %2
+OpStore %2 %5
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  OptimizerOptions()->preserve_spec_constants_ = true;
+  SinglePassRunAndCheck<AggressiveDCEPass>(spirv, spirv, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp
index 654e880..f87fd80 100644
--- a/test/opt/block_merge_test.cpp
+++ b/test/opt/block_merge_test.cpp
@@ -483,6 +483,8 @@
 ; CHECK-NOT: OpLoopMerge
 ; CHECK: OpReturn
 ; CHECK: [[continue:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
 ; CHECK-NEXT: OpBranch [[header]]
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -588,7 +590,7 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndMatch<BlockMergePass>(text, true);
+  SinglePassRunAndMatch<BlockMergePass>(text, false);
 }
 
 TEST_F(BlockMergeTest, DontMergeReturn) {
@@ -741,6 +743,186 @@
   SinglePassRunAndMatch<BlockMergePass>(text, true);
 }
 
+TEST_F(BlockMergeTest, OpPhiInSuccessor) {
+  // Checks that when merging blocks A and B, the OpPhi at the start of B is
+  // removed and uses of its definition are replaced appropriately.
+  const std::string prefix =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %x "x"
+OpName %y "y"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %6
+%10 = OpLabel
+%x = OpVariable %_ptr_Function_int Function
+%y = OpVariable %_ptr_Function_int Function
+OpStore %x %int_1
+%11 = OpLoad %int %x
+)";
+
+  const std::string suffix_before =
+      R"(OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %int %11 %10
+OpStore %y %13
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string suffix_after =
+      R"(OpStore %y %11
+OpReturn
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<BlockMergePass>(prefix + suffix_before,
+                                        prefix + suffix_after, true, true);
+}
+
+TEST_F(BlockMergeTest, MultipleOpPhisInSuccessor) {
+  // Checks that when merging blocks A and B, the OpPhis at the start of B are
+  // removed and uses of their definitions are replaced appropriately.
+  const std::string prefix =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %S "S"
+OpMemberName %S 0 "x"
+OpMemberName %S 1 "f"
+OpName %s "s"
+OpName %g "g"
+OpName %y "y"
+OpName %t "t"
+OpName %z "z"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%float = OpTypeFloat 32
+%S = OpTypeStruct %int %float
+%_ptr_Function_S = OpTypePointer Function %S
+%int_1 = OpConstant %int 1
+%float_2 = OpConstant %float 2
+%16 = OpConstantComposite %S %int_1 %float_2
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Function_int = OpTypePointer Function %int
+%int_3 = OpConstant %int 3
+%int_0 = OpConstant %int 0
+%main = OpFunction %void None %10
+%21 = OpLabel
+%s = OpVariable %_ptr_Function_S Function
+%g = OpVariable %_ptr_Function_float Function
+%y = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_S Function
+%z = OpVariable %_ptr_Function_float Function
+OpStore %s %16
+OpStore %g %float_2
+OpStore %y %int_3
+%22 = OpLoad %S %s
+OpStore %t %22
+%23 = OpAccessChain %_ptr_Function_float %s %int_1
+%24 = OpLoad %float %23
+%25 = OpLoad %float %g
+)";
+
+  const std::string suffix_before =
+      R"(OpBranch %26
+%26 = OpLabel
+%27 = OpPhi %float %24 %21
+%28 = OpPhi %float %25 %21
+%29 = OpFAdd %float %27 %28
+%30 = OpAccessChain %_ptr_Function_int %s %int_0
+%31 = OpLoad %int %30
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %float %29 %26
+%34 = OpPhi %int %31 %26
+%35 = OpConvertSToF %float %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpPhi %float %35 %32
+%38 = OpFSub %float %33 %37
+%39 = OpLoad %int %y
+OpBranch %40
+%40 = OpLabel
+%41 = OpPhi %float %38 %36
+%42 = OpPhi %int %39 %36
+%43 = OpConvertSToF %float %42
+%44 = OpFAdd %float %41 %43
+OpStore %z %44
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string suffix_after =
+      R"(%29 = OpFAdd %float %24 %25
+%30 = OpAccessChain %_ptr_Function_int %s %int_0
+%31 = OpLoad %int %30
+%35 = OpConvertSToF %float %31
+%38 = OpFSub %float %29 %35
+%39 = OpLoad %int %y
+%43 = OpConvertSToF %float %39
+%44 = OpFAdd %float %38 %43
+OpStore %z %44
+OpReturn
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<BlockMergePass>(prefix + suffix_before,
+                                        prefix + suffix_after, true, true);
+}
+
+TEST_F(BlockMergeTest, UnreachableLoop) {
+  const std::string spirv = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpLoopMerge %12 %13 None
+OpBranchConditional %false %13 %14
+%13 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %false %16 %17
+%16 = OpLabel
+OpBranch %15
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpBranch %11
+%14 = OpLabel
+OpReturn
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<BlockMergePass>(spirv, spirv, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
index 20d883b..2ee7cce 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -896,6 +896,35 @@
   SinglePassRunAndMatch<CCPPass>(text, true);
 }
 
+TEST_F(CCPTest, FoldWithDecoration) {
+  const std::string text = R"(
+; CHECK: OpCapability
+; CHECK-NOT: OpDecorate
+; CHECK: OpFunctionEnd
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %3 RelaxedPrecision
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+         %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+          %2 = OpFunction %void None %5
+         %11 = OpLabel
+          %3 = OpVectorShuffle %v3float %10 %10 0 1 2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/cfg_test.cpp b/test/opt/cfg_test.cpp
new file mode 100644
index 0000000..2cfc9f3
--- /dev/null
+++ b/test/opt/cfg_test.cpp
@@ -0,0 +1,205 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/ir_context.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::ContainerEq;
+
+using CFGTest = PassTest<::testing::Test>;
+
+TEST_F(CFGTest, ForEachBlockInPostOrderIf) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %true %9 %10
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInPostOrder(&*function->begin(), [&order](BasicBlock* bb) {
+    order.push_back(bb->id());
+  });
+
+  std::vector<uint32_t> expected_result = {10, 9, 8};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
+TEST_F(CFGTest, ForEachBlockInPostOrderLoop) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %10 None
+OpBranchConditional %true %11 %10
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInPostOrder(&*function->begin(), [&order](BasicBlock* bb) {
+    order.push_back(bb->id());
+  });
+
+  std::vector<uint32_t> expected_result1 = {10, 11, 9, 8};
+  std::vector<uint32_t> expected_result2 = {11, 10, 9, 8};
+  EXPECT_THAT(order, AnyOf(ContainerEq(expected_result1),
+                           ContainerEq(expected_result2)));
+}
+
+TEST_F(CFGTest, ForEachBlockInReversePostOrderIf) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %true %9 %10
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInReversePostOrder(
+      &*function->begin(),
+      [&order](BasicBlock* bb) { order.push_back(bb->id()); });
+
+  std::vector<uint32_t> expected_result = {8, 9, 10};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
+TEST_F(CFGTest, ForEachBlockInReversePostOrderLoop) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %10 None
+OpBranchConditional %true %11 %10
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInReversePostOrder(
+      &*function->begin(),
+      [&order](BasicBlock* bb) { order.push_back(bb->id()); });
+
+  std::vector<uint32_t> expected_result1 = {8, 9, 10, 11};
+  std::vector<uint32_t> expected_result2 = {8, 9, 11, 10};
+  EXPECT_THAT(order, AnyOf(ContainerEq(expected_result1),
+                           ContainerEq(expected_result2)));
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/code_sink_test.cpp b/test/opt/code_sink_test.cpp
index 9b86c66..f1bd127 100644
--- a/test/opt/code_sink_test.cpp
+++ b/test/opt/code_sink_test.cpp
@@ -528,6 +528,30 @@
   EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
 }
 
+TEST_F(CodeSinkTest, DecorationOnLoad) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main" %2
+               OpDecorate %3 RelaxedPrecision
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+          %2 = OpVariable %_ptr_Input_float Input
+          %1 = OpFunction %void None %5
+          %8 = OpLabel
+          %3 = OpLoad %float %2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // We just want to make sure the code does not crash.
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/common_uniform_elim_test.cpp b/test/opt/common_uniform_elim_test.cpp
deleted file mode 100644
index 9e39439..0000000
--- a/test/opt/common_uniform_elim_test.cpp
+++ /dev/null
@@ -1,1394 +0,0 @@
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 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 <string>
-
-#include "test/opt/pass_fixture.h"
-
-namespace spvtools {
-namespace opt {
-namespace {
-
-using CommonUniformElimTest = PassTest<::testing::Test>;
-
-TEST_F(CommonUniformElimTest, Basic1) {
-  // Note: This test exemplifies the following:
-  // - Common uniform (%_) load floated to nearest non-controlled block
-  // - Common extract (g_F) floated to non-controlled block
-  // - Non-common extract (g_F2) not floated, but common uniform load shared
-  //
-  // #version 140
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     float g_F;
-  //     float g_F2;
-  // } ;
-  //
-  // void main()
-  // {
-  //     vec4 v = BaseColor;
-  //     if (fi > 0) {
-  //       v = v * g_F;
-  //     }
-  //     else {
-  //       float f2 = g_F2 - g_F;
-  //       v = v * f2;
-  //     }
-  //     gl_FragColor = v;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %fi "fi"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpMemberName %U_t 1 "g_F2"
-OpName %_ ""
-OpName %f2 "f2"
-OpName %gl_FragColor "gl_FragColor"
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%11 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%U_t = OpTypeStruct %float %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Function_float = OpTypePointer Function %float
-%int_1 = OpConstant %int 1
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %11
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpLoad %float %fi
-%29 = OpFOrdGreaterThan %bool %28 %float_0
-OpSelectionMerge %30 None
-OpBranchConditional %29 %31 %32
-%31 = OpLabel
-%33 = OpLoad %v4float %v
-%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%35 = OpLoad %float %34
-%36 = OpVectorTimesScalar %v4float %33 %35
-OpStore %v %36
-OpBranch %30
-%32 = OpLabel
-%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%38 = OpLoad %float %37
-%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%40 = OpLoad %float %39
-%41 = OpFSub %float %38 %40
-OpStore %f2 %41
-%42 = OpLoad %v4float %v
-%43 = OpLoad %float %f2
-%44 = OpVectorTimesScalar %v4float %42 %43
-OpStore %v %44
-OpBranch %30
-%30 = OpLabel
-%45 = OpLoad %v4float %v
-OpStore %gl_FragColor %45
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %11
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%52 = OpLoad %U_t %_
-%53 = OpCompositeExtract %float %52 0
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpLoad %float %fi
-%29 = OpFOrdGreaterThan %bool %28 %float_0
-OpSelectionMerge %30 None
-OpBranchConditional %29 %31 %32
-%31 = OpLabel
-%33 = OpLoad %v4float %v
-%36 = OpVectorTimesScalar %v4float %33 %53
-OpStore %v %36
-OpBranch %30
-%32 = OpLabel
-%49 = OpCompositeExtract %float %52 1
-%41 = OpFSub %float %49 %53
-OpStore %f2 %41
-%42 = OpLoad %v4float %v
-%43 = OpLoad %float %f2
-%44 = OpVectorTimesScalar %v4float %42 %43
-OpStore %v %44
-OpBranch %30
-%30 = OpLabel
-%45 = OpLoad %v4float %v
-OpStore %gl_FragColor %45
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, Basic2) {
-  // Note: This test exemplifies the following:
-  // - Common uniform (%_) load floated to nearest non-controlled block
-  // - Common extract (g_F) floated to non-controlled block
-  // - Non-common extract (g_F2) not floated, but common uniform load shared
-  //
-  // #version 140
-  // in vec4 BaseColor;
-  // in float fi;
-  // in float fi2;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     float g_F;
-  //     float g_F2;
-  // } ;
-  //
-  // void main()
-  // {
-  //     float f = fi;
-  //     if (f < 0)
-  //       f = -f;
-  //     if (fi2 > 0) {
-  //       f = f * g_F;
-  //     }
-  //     else {
-  //       f = g_F2 - g_F;
-  //     }
-  //     gl_FragColor = f * BaseColor;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %fi %fi2 %gl_FragColor %BaseColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %f "f"
-OpName %fi "fi"
-OpName %fi2 "fi2"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpMemberName %U_t 1 "g_F2"
-OpName %_ ""
-OpName %gl_FragColor "gl_FragColor"
-OpName %BaseColor "BaseColor"
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%11 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Function_float = OpTypePointer Function %float
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%fi2 = OpVariable %_ptr_Input_float Input
-%U_t = OpTypeStruct %float %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%int_1 = OpConstant %int 1
-%v4float = OpTypeVector %float 4
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %11
-%25 = OpLabel
-%f = OpVariable %_ptr_Function_float Function
-%26 = OpLoad %float %fi
-OpStore %f %26
-%27 = OpLoad %float %f
-%28 = OpFOrdLessThan %bool %27 %float_0
-OpSelectionMerge %29 None
-OpBranchConditional %28 %30 %29
-%30 = OpLabel
-%31 = OpLoad %float %f
-%32 = OpFNegate %float %31
-OpStore %f %32
-OpBranch %29
-%29 = OpLabel
-%33 = OpLoad %float %fi2
-%34 = OpFOrdGreaterThan %bool %33 %float_0
-OpSelectionMerge %35 None
-OpBranchConditional %34 %36 %37
-%36 = OpLabel
-%38 = OpLoad %float %f
-%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%40 = OpLoad %float %39
-%41 = OpFMul %float %38 %40
-OpStore %f %41
-OpBranch %35
-%37 = OpLabel
-%42 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%43 = OpLoad %float %42
-%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%45 = OpLoad %float %44
-%46 = OpFSub %float %43 %45
-OpStore %f %46
-OpBranch %35
-%35 = OpLabel
-%47 = OpLoad %v4float %BaseColor
-%48 = OpLoad %float %f
-%49 = OpVectorTimesScalar %v4float %47 %48
-OpStore %gl_FragColor %49
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %11
-%25 = OpLabel
-%f = OpVariable %_ptr_Function_float Function
-%26 = OpLoad %float %fi
-OpStore %f %26
-%27 = OpLoad %float %f
-%28 = OpFOrdLessThan %bool %27 %float_0
-OpSelectionMerge %29 None
-OpBranchConditional %28 %30 %29
-%30 = OpLabel
-%31 = OpLoad %float %f
-%32 = OpFNegate %float %31
-OpStore %f %32
-OpBranch %29
-%29 = OpLabel
-%56 = OpLoad %U_t %_
-%57 = OpCompositeExtract %float %56 0
-%33 = OpLoad %float %fi2
-%34 = OpFOrdGreaterThan %bool %33 %float_0
-OpSelectionMerge %35 None
-OpBranchConditional %34 %36 %37
-%36 = OpLabel
-%38 = OpLoad %float %f
-%41 = OpFMul %float %38 %57
-OpStore %f %41
-OpBranch %35
-%37 = OpLabel
-%53 = OpCompositeExtract %float %56 1
-%46 = OpFSub %float %53 %57
-OpStore %f %46
-OpBranch %35
-%35 = OpLabel
-%47 = OpLoad %v4float %BaseColor
-%48 = OpLoad %float %f
-%49 = OpVectorTimesScalar %v4float %47 %48
-OpStore %gl_FragColor %49
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, Basic3) {
-  // Note: This test exemplifies the following:
-  // - Existing common uniform (%_) load kept in place and shared
-  //
-  // #version 140
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     bool g_B;
-  //     float g_F;
-  // } ;
-  //
-  // void main()
-  // {
-  //     vec4 v = BaseColor;
-  //     if (g_B)
-  //       v = v * g_F;
-  //     gl_FragColor = v;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor %fi
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_B"
-OpMemberName %U_t 1 "g_F"
-OpName %_ ""
-OpName %gl_FragColor "gl_FragColor"
-OpName %fi "fi"
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%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
-%uint = OpTypeInt 32 0
-%U_t = OpTypeStruct %uint %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%int_1 = OpConstant %int 1
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %10
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
-%29 = OpLoad %uint %28
-%30 = OpINotEqual %bool %29 %uint_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
-%32 = OpLabel
-%33 = OpLoad %v4float %v
-%34 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%35 = OpLoad %float %34
-%36 = OpVectorTimesScalar %v4float %33 %35
-OpStore %v %36
-OpBranch %31
-%31 = OpLabel
-%37 = OpLoad %v4float %v
-OpStore %gl_FragColor %37
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %10
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%38 = OpLoad %U_t %_
-%39 = OpCompositeExtract %uint %38 0
-%30 = OpINotEqual %bool %39 %uint_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
-%32 = OpLabel
-%33 = OpLoad %v4float %v
-%41 = OpCompositeExtract %float %38 1
-%36 = OpVectorTimesScalar %v4float %33 %41
-OpStore %v %36
-OpBranch %31
-%31 = OpLabel
-%37 = OpLoad %v4float %v
-OpStore %gl_FragColor %37
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, Loop) {
-  // Note: This test exemplifies the following:
-  // - Common extract (g_F) shared between two loops
-  // #version 140
-  // in vec4 BC;
-  // in vec4 BC2;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     float g_F;
-  // } ;
-  //
-  // void main()
-  // {
-  //     vec4 v = BC;
-  //     for (int i = 0; i < 4; i++)
-  //       v[i] = v[i] / g_F;
-  //     vec4 v2 = BC2;
-  //     for (int i = 0; i < 4; i++)
-  //       v2[i] = v2[i] * g_F;
-  //     gl_FragColor = v + v2;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BC %BC2 %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %v "v"
-OpName %BC "BC"
-OpName %i "i"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpName %_ ""
-OpName %v2 "v2"
-OpName %BC2 "BC2"
-OpName %i_0 "i"
-OpName %gl_FragColor "gl_FragColor"
-OpMemberDecorate %U_t 0 Offset 0
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%13 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BC = OpVariable %_ptr_Input_v4float Input
-%int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
-%int_0 = OpConstant %int 0
-%int_4 = OpConstant %int 4
-%bool = OpTypeBool
-%_ptr_Function_float = OpTypePointer Function %float
-%U_t = OpTypeStruct %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%int_1 = OpConstant %int 1
-%BC2 = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %13
-%28 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%i = OpVariable %_ptr_Function_int Function
-%v2 = OpVariable %_ptr_Function_v4float Function
-%i_0 = OpVariable %_ptr_Function_int Function
-%29 = OpLoad %v4float %BC
-OpStore %v %29
-OpStore %i %int_0
-OpBranch %30
-%30 = OpLabel
-OpLoopMerge %31 %32 None
-OpBranch %33
-%33 = OpLabel
-%34 = OpLoad %int %i
-%35 = OpSLessThan %bool %34 %int_4
-OpBranchConditional %35 %36 %31
-%36 = OpLabel
-%37 = OpLoad %int %i
-%38 = OpLoad %int %i
-%39 = OpAccessChain %_ptr_Function_float %v %38
-%40 = OpLoad %float %39
-%41 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%42 = OpLoad %float %41
-%43 = OpFDiv %float %40 %42
-%44 = OpAccessChain %_ptr_Function_float %v %37
-OpStore %44 %43
-OpBranch %32
-%32 = OpLabel
-%45 = OpLoad %int %i
-%46 = OpIAdd %int %45 %int_1
-OpStore %i %46
-OpBranch %30
-%31 = OpLabel
-%47 = OpLoad %v4float %BC2
-OpStore %v2 %47
-OpStore %i_0 %int_0
-OpBranch %48
-%48 = OpLabel
-OpLoopMerge %49 %50 None
-OpBranch %51
-%51 = OpLabel
-%52 = OpLoad %int %i_0
-%53 = OpSLessThan %bool %52 %int_4
-OpBranchConditional %53 %54 %49
-%54 = OpLabel
-%55 = OpLoad %int %i_0
-%56 = OpLoad %int %i_0
-%57 = OpAccessChain %_ptr_Function_float %v2 %56
-%58 = OpLoad %float %57
-%59 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%60 = OpLoad %float %59
-%61 = OpFMul %float %58 %60
-%62 = OpAccessChain %_ptr_Function_float %v2 %55
-OpStore %62 %61
-OpBranch %50
-%50 = OpLabel
-%63 = OpLoad %int %i_0
-%64 = OpIAdd %int %63 %int_1
-OpStore %i_0 %64
-OpBranch %48
-%49 = OpLabel
-%65 = OpLoad %v4float %v
-%66 = OpLoad %v4float %v2
-%67 = OpFAdd %v4float %65 %66
-OpStore %gl_FragColor %67
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %13
-%28 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%i = OpVariable %_ptr_Function_int Function
-%v2 = OpVariable %_ptr_Function_v4float Function
-%i_0 = OpVariable %_ptr_Function_int Function
-%72 = OpLoad %U_t %_
-%73 = OpCompositeExtract %float %72 0
-%29 = OpLoad %v4float %BC
-OpStore %v %29
-OpStore %i %int_0
-OpBranch %30
-%30 = OpLabel
-OpLoopMerge %31 %32 None
-OpBranch %33
-%33 = OpLabel
-%34 = OpLoad %int %i
-%35 = OpSLessThan %bool %34 %int_4
-OpBranchConditional %35 %36 %31
-%36 = OpLabel
-%37 = OpLoad %int %i
-%38 = OpLoad %int %i
-%39 = OpAccessChain %_ptr_Function_float %v %38
-%40 = OpLoad %float %39
-%43 = OpFDiv %float %40 %73
-%44 = OpAccessChain %_ptr_Function_float %v %37
-OpStore %44 %43
-OpBranch %32
-%32 = OpLabel
-%45 = OpLoad %int %i
-%46 = OpIAdd %int %45 %int_1
-OpStore %i %46
-OpBranch %30
-%31 = OpLabel
-%47 = OpLoad %v4float %BC2
-OpStore %v2 %47
-OpStore %i_0 %int_0
-OpBranch %48
-%48 = OpLabel
-OpLoopMerge %49 %50 None
-OpBranch %51
-%51 = OpLabel
-%52 = OpLoad %int %i_0
-%53 = OpSLessThan %bool %52 %int_4
-OpBranchConditional %53 %54 %49
-%54 = OpLabel
-%55 = OpLoad %int %i_0
-%56 = OpLoad %int %i_0
-%57 = OpAccessChain %_ptr_Function_float %v2 %56
-%58 = OpLoad %float %57
-%61 = OpFMul %float %58 %73
-%62 = OpAccessChain %_ptr_Function_float %v2 %55
-OpStore %62 %61
-OpBranch %50
-%50 = OpLabel
-%63 = OpLoad %int %i_0
-%64 = OpIAdd %int %63 %int_1
-OpStore %i_0 %64
-OpBranch %48
-%49 = OpLabel
-%65 = OpLoad %v4float %v
-%66 = OpLoad %v4float %v2
-%67 = OpFAdd %v4float %65 %66
-OpStore %gl_FragColor %67
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, Volatile1) {
-  // Note: This test exemplifies the following:
-  // - Same test as Basic1 with the exception that
-  //   the Load of g_F in else-branch is volatile
-  // - Common uniform (%_) load floated to nearest non-controlled block
-  //
-  // #version 140
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     float g_F;
-  //     float g_F2;
-  // } ;
-  //
-  // void main()
-  // {
-  //     vec4 v = BaseColor;
-  //     if (fi > 0) {
-  //       v = v * g_F;
-  //     }
-  //     else {
-  //       float f2 = g_F2 - g_F;
-  //       v = v * f2;
-  //     }
-  //     gl_FragColor = v;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %fi "fi"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpMemberName %U_t 1 "g_F2"
-OpName %_ ""
-OpName %f2 "f2"
-OpName %gl_FragColor "gl_FragColor"
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%11 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%U_t = OpTypeStruct %float %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Function_float = OpTypePointer Function %float
-%int_1 = OpConstant %int 1
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %11
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpLoad %float %fi
-%29 = OpFOrdGreaterThan %bool %28 %float_0
-OpSelectionMerge %30 None
-OpBranchConditional %29 %31 %32
-%31 = OpLabel
-%33 = OpLoad %v4float %v
-%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%35 = OpLoad %float %34
-%36 = OpVectorTimesScalar %v4float %33 %35
-OpStore %v %36
-OpBranch %30
-%32 = OpLabel
-%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%38 = OpLoad %float %37
-%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%40 = OpLoad %float %39 Volatile
-%41 = OpFSub %float %38 %40
-OpStore %f2 %41
-%42 = OpLoad %v4float %v
-%43 = OpLoad %float %f2
-%44 = OpVectorTimesScalar %v4float %42 %43
-OpStore %v %44
-OpBranch %30
-%30 = OpLabel
-%45 = OpLoad %v4float %v
-OpStore %gl_FragColor %45
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %11
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%50 = OpLoad %U_t %_
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpLoad %float %fi
-%29 = OpFOrdGreaterThan %bool %28 %float_0
-OpSelectionMerge %30 None
-OpBranchConditional %29 %31 %32
-%31 = OpLabel
-%33 = OpLoad %v4float %v
-%47 = OpCompositeExtract %float %50 0
-%36 = OpVectorTimesScalar %v4float %33 %47
-OpStore %v %36
-OpBranch %30
-%32 = OpLabel
-%49 = OpCompositeExtract %float %50 1
-%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%40 = OpLoad %float %39 Volatile
-%41 = OpFSub %float %49 %40
-OpStore %f2 %41
-%42 = OpLoad %v4float %v
-%43 = OpLoad %float %f2
-%44 = OpVectorTimesScalar %v4float %42 %43
-OpStore %v %44
-OpBranch %30
-%30 = OpLabel
-%45 = OpLoad %v4float %v
-OpStore %gl_FragColor %45
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, Volatile2) {
-  // Note: This test exemplifies the following:
-  // - Same test as Basic1 with the exception that
-  //   U_t is Volatile.
-  // - No optimizations are applied
-  //
-  // #version 430
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // layout(std430) volatile buffer U_t
-  // {
-  //   float g_F;
-  //   float g_F2;
-  // };
-  //
-  //
-  // void main(void)
-  // {
-  //   vec4 v = BaseColor;
-  //   if (fi > 0) {
-  //     v = v * g_F;
-  //   } else {
-  //     float f2 = g_F2 - g_F;
-  //     v = v * f2;
-  //   }
-  // }
-
-  const std::string text =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %fi
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 430
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %fi "fi"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpMemberName %U_t 1 "g_F2"
-OpName %_ ""
-OpName %f2 "f2"
-OpDecorate %BaseColor Location 0
-OpDecorate %fi Location 0
-OpMemberDecorate %U_t 0 Volatile
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Volatile
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t BufferBlock
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%U_t = OpTypeStruct %float %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Function_float = OpTypePointer Function %float
-%int_1 = OpConstant %int 1
-%main = OpFunction %void None %3
-%5 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%12 = OpLoad %v4float %BaseColor
-OpStore %v %12
-%15 = OpLoad %float %fi
-%18 = OpFOrdGreaterThan %bool %15 %float_0
-OpSelectionMerge %20 None
-OpBranchConditional %18 %19 %31
-%19 = OpLabel
-%21 = OpLoad %v4float %v
-%28 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%29 = OpLoad %float %28
-%30 = OpVectorTimesScalar %v4float %21 %29
-OpStore %v %30
-OpBranch %20
-%31 = OpLabel
-%35 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%36 = OpLoad %float %35
-%37 = OpAccessChain %_ptr_Uniform_float %_ %int_0
-%38 = OpLoad %float %37
-%39 = OpFSub %float %36 %38
-OpStore %f2 %39
-%40 = OpLoad %v4float %v
-%41 = OpLoad %float %f2
-%42 = OpVectorTimesScalar %v4float %40 %41
-OpStore %v %42
-OpBranch %20
-%20 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  Pass::Status res = std::get<1>(
-      SinglePassRunAndDisassemble<CommonUniformElimPass>(text, true, false));
-  EXPECT_EQ(res, Pass::Status::SuccessWithoutChange);
-}
-
-TEST_F(CommonUniformElimTest, Volatile3) {
-  // Note: This test exemplifies the following:
-  // - Same test as Volatile2 with the exception that
-  //   the nested struct S is volatile
-  // - No optimizations are applied
-  //
-  // #version 430
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // struct S {
-  //   volatile float a;
-  // };
-  //
-  // layout(std430) buffer U_t
-  // {
-  //   S g_F;
-  //   S g_F2;
-  // };
-  //
-  //
-  // void main(void)
-  // {
-  //   vec4 v = BaseColor;
-  //   if (fi > 0) {
-  //     v = v * g_F.a;
-  //   } else {
-  //     float f2 = g_F2.a - g_F.a;
-  //     v = v * f2;
-  //   }
-  // }
-
-  const std::string text =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %fi
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 430
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %fi "fi"
-OpName %S "S"
-OpMemberName %S 0 "a"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpMemberName %U_t 1 "g_F2"
-OpName %_ ""
-OpName %f2 "f2"
-OpDecorate %BaseColor Location 0
-OpDecorate %fi Location 0
-OpMemberDecorate %S 0 Offset 0
-OpMemberDecorate %S 0 Volatile
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t BufferBlock
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%S = OpTypeStruct %float
-%U_t = OpTypeStruct %S %S
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Function_float = OpTypePointer Function %float
-%int_1 = OpConstant %int 1
-%main = OpFunction %void None %3
-%5 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%f2 = OpVariable %_ptr_Function_float Function
-%12 = OpLoad %v4float %BaseColor
-OpStore %v %12
-%15 = OpLoad %float %fi
-%18 = OpFOrdGreaterThan %bool %15 %float_0
-OpSelectionMerge %20 None
-OpBranchConditional %18 %19 %32
-%19 = OpLabel
-%21 = OpLoad %v4float %v
-%29 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
-%30 = OpLoad %float %29
-%31 = OpVectorTimesScalar %v4float %21 %30
-OpStore %v %31
-OpBranch %20
-%32 = OpLabel
-%36 = OpAccessChain %_ptr_Uniform_float %_ %int_1 %int_0
-%37 = OpLoad %float %36
-%38 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
-%39 = OpLoad %float %38
-%40 = OpFSub %float %37 %39
-OpStore %f2 %40
-%41 = OpLoad %v4float %v
-%42 = OpLoad %float %f2
-%43 = OpVectorTimesScalar %v4float %41 %42
-OpStore %v %43
-OpBranch %20
-%20 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  Pass::Status res = std::get<1>(
-      SinglePassRunAndDisassemble<CommonUniformElimPass>(text, true, false));
-  EXPECT_EQ(res, Pass::Status::SuccessWithoutChange);
-}
-
-TEST_F(CommonUniformElimTest, IteratorDanglingPointer) {
-  // Note: This test exemplifies the following:
-  // - Existing common uniform (%_) load kept in place and shared
-  //
-  // #version 140
-  // in vec4 BaseColor;
-  // in float fi;
-  //
-  // layout(std140) uniform U_t
-  // {
-  //     bool g_B;
-  //     float g_F;
-  // } ;
-  //
-  // uniform float alpha;
-  // uniform bool alpha_B;
-  //
-  // void main()
-  // {
-  //     vec4 v = BaseColor;
-  //     if (g_B) {
-  //       v = v * g_F;
-  //       if (alpha_B)
-  //         v = v * alpha;
-  //       else
-  //         v = v * fi;
-  //     }
-  //     gl_FragColor = v;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor %fi
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_B"
-OpMemberName %U_t 1 "g_F"
-OpName %alpha "alpha"
-OpName %alpha_B "alpha_B"
-OpName %_ ""
-OpName %gl_FragColor "gl_FragColor"
-OpName %fi "fi"
-OpMemberDecorate %U_t 0 Offset 0
-OpMemberDecorate %U_t 1 Offset 4
-OpDecorate %U_t Block
-OpDecorate %_ DescriptorSet 0
-%void = OpTypeVoid
-%12 = 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
-%uint = OpTypeInt 32 0
-%U_t = OpTypeStruct %uint %float
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%int_1 = OpConstant %int 1
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%_ptr_Input_float = OpTypePointer Input %float
-%fi = OpVariable %_ptr_Input_float Input
-%alpha = OpVariable %_ptr_Uniform_float Uniform
-%alpha_B = OpVariable %_ptr_Uniform_uint Uniform
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %12
-%26 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%27 = OpLoad %v4float %BaseColor
-OpStore %v %27
-%28 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
-%29 = OpLoad %uint %28
-%30 = OpINotEqual %bool %29 %uint_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %31 %32
-%32 = OpLabel
-%47 = OpLoad %v4float %v
-OpStore %gl_FragColor %47
-OpReturn
-%31 = OpLabel
-%33 = OpAccessChain %_ptr_Uniform_float %_ %int_1
-%34 = OpLoad %float %33
-%35 = OpLoad %v4float %v
-%36 = OpVectorTimesScalar %v4float %35 %34
-OpStore %v %36
-%37 = OpLoad %uint %alpha_B
-%38 = OpIEqual %bool %37 %uint_0
-OpSelectionMerge %43 None
-OpBranchConditional %38 %43 %39
-%39 = OpLabel
-%40 = OpLoad %float %alpha
-%41 = OpLoad %v4float %v
-%42 = OpVectorTimesScalar %v4float %41 %40
-OpStore %v %42
-OpBranch %50
-%50 = OpLabel
-%51 = OpLoad %v4float %v
-OpStore %gl_FragColor %51
-OpReturn
-%43 = OpLabel
-%44 = OpLoad %float %fi
-%45 = OpLoad %v4float %v
-%46 = OpVectorTimesScalar %v4float %45 %44
-OpStore %v %46
-OpBranch %60
-%60 = OpLabel
-%61 = OpLoad %v4float %v
-OpStore %gl_FragColor %61
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %12
-%28 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%29 = OpLoad %v4float %BaseColor
-OpStore %v %29
-%54 = OpLoad %U_t %_
-%55 = OpCompositeExtract %uint %54 0
-%32 = OpINotEqual %bool %55 %uint_0
-OpSelectionMerge %33 None
-OpBranchConditional %32 %33 %34
-%34 = OpLabel
-%35 = OpLoad %v4float %v
-OpStore %gl_FragColor %35
-OpReturn
-%33 = OpLabel
-%58 = OpLoad %float %alpha
-%57 = OpCompositeExtract %float %54 1
-%38 = OpLoad %v4float %v
-%39 = OpVectorTimesScalar %v4float %38 %57
-OpStore %v %39
-%40 = OpLoad %uint %alpha_B
-%41 = OpIEqual %bool %40 %uint_0
-OpSelectionMerge %42 None
-OpBranchConditional %41 %42 %43
-%43 = OpLabel
-%45 = OpLoad %v4float %v
-%46 = OpVectorTimesScalar %v4float %45 %58
-OpStore %v %46
-OpBranch %47
-%47 = OpLabel
-%48 = OpLoad %v4float %v
-OpStore %gl_FragColor %48
-OpReturn
-%42 = OpLabel
-%49 = OpLoad %float %fi
-%50 = OpLoad %v4float %v
-%51 = OpVectorTimesScalar %v4float %50 %49
-OpStore %v %51
-OpBranch %52
-%52 = OpLabel
-%53 = OpLoad %v4float %v
-OpStore %gl_FragColor %53
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
-                                               predefs + after, true, true);
-}
-
-TEST_F(CommonUniformElimTest, MixedConstantAndNonConstantIndexes) {
-  const std::string text = R"(
-; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform
-; CHECK: %501 = OpLabel
-; CHECK: [[ld:%\w+]] = OpLoad
-; CHECK-NOT: OpCompositeExtract {{%\w+}} {{%\w+}} 0 2 484
-; CHECK: OpAccessChain {{%\w+}} [[var]] %int_0 %int_2 [[ld]]
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "ringeffectLayer_px" %gl_FragCoord %178 %182
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource HLSL 500
-               OpDecorate %_arr_v4float_uint_10 ArrayStride 16
-               OpMemberDecorate %_struct_20 0 Offset 0
-               OpMemberDecorate %_struct_20 1 Offset 16
-               OpMemberDecorate %_struct_20 2 Offset 32
-               OpMemberDecorate %_struct_21 0 Offset 0
-               OpDecorate %_struct_21 Block
-               OpDecorate %23 DescriptorSet 0
-               OpDecorate %gl_FragCoord BuiltIn FragCoord
-               OpDecorate %178 Location 0
-               OpDecorate %182 Location 0
-       %void = OpTypeVoid
-          %3 = OpTypeFunction %void
-      %float = OpTypeFloat 32
-    %v4float = OpTypeVector %float 4
-    %v2float = OpTypeVector %float 2
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-       %uint = OpTypeInt 32 0
-    %uint_10 = OpConstant %uint 10
-%_arr_v4float_uint_10 = OpTypeArray %v4float %uint_10
- %_struct_20 = OpTypeStruct %v4float %v4float %_arr_v4float_uint_10
- %_struct_21 = OpTypeStruct %_struct_20
-%_ptr_Uniform__struct_21 = OpTypePointer Uniform %_struct_21
-         %23 = OpVariable %_ptr_Uniform__struct_21 Uniform
-        %int = OpTypeInt 32 1
-      %int_0 = OpConstant %int 0
-%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-     %uint_3 = OpConstant %uint 3
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-    %float_0 = OpConstant %float 0
-         %43 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-%_ptr_Function_int = OpTypePointer Function %int
-      %int_5 = OpConstant %int 5
-       %bool = OpTypeBool
-      %int_1 = OpConstant %int 1
-      %int_2 = OpConstant %int 2
-     %uint_5 = OpConstant %uint 5
-%_arr_v2float_uint_5 = OpTypeArray %v2float %uint_5
-%_ptr_Function__arr_v2float_uint_5 = OpTypePointer Function %_arr_v2float_uint_5
-         %82 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%_ptr_UniformConstant_82 = OpTypePointer UniformConstant %82
-         %86 = OpTypeSampler
-%_ptr_UniformConstant_86 = OpTypePointer UniformConstant %86
-         %90 = OpTypeSampledImage %82
-    %v3float = OpTypeVector %float 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-        %178 = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-        %182 = OpVariable %_ptr_Output_v4float Output
-          %4 = OpFunction %void None %3
-          %5 = OpLabel
-        %483 = OpVariable %_ptr_Function_v4float Function
-        %484 = OpVariable %_ptr_Function_int Function
-        %486 = OpVariable %_ptr_Function__arr_v2float_uint_5 Function
-        %179 = OpLoad %v4float %178
-        %493 = OpAccessChain %_ptr_Uniform_float %23 %int_0 %int_0 %uint_3
-        %494 = OpLoad %float %493
-               OpStore %483 %43
-               OpStore %484 %int_0
-               OpBranch %495
-        %495 = OpLabel
-               OpLoopMerge %496 %497 None
-               OpBranch %498
-        %498 = OpLabel
-        %499 = OpLoad %int %484
-        %500 = OpSLessThan %bool %499 %int_5
-               OpBranchConditional %500 %501 %496
-        %501 = OpLabel
-        %504 = OpVectorShuffle %v2float %179 %179 0 1
-        %505 = OpLoad %int %484
-        %506 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_2 %505
-        %507 = OpLoad %v4float %506
-        %508 = OpVectorShuffle %v2float %507 %507 0 1
-        %509 = OpFAdd %v2float %504 %508
-        %512 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_1
-        %513 = OpLoad %v4float %512
-        %514 = OpVectorShuffle %v2float %513 %513 0 1
-        %517 = OpVectorShuffle %v2float %513 %513 2 3
-        %518 = OpExtInst %v2float %1 FClamp %509 %514 %517
-        %519 = OpAccessChain %_ptr_Function_v2float %486 %505
-               OpStore %519 %518
-               OpBranch %497
-        %497 = OpLabel
-        %520 = OpLoad %int %484
-        %521 = OpIAdd %int %520 %int_1
-               OpStore %484 %521
-               OpBranch %495
-        %496 = OpLabel
-               OpReturn
-               OpFunctionEnd
-)";
-
-  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
-}
-
-TEST_F(CommonUniformElimTest, LoadPlacedAfterPhi) {
-  const std::string text = R"(
-; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform
-; CHECK: OpSelectionMerge [[merge:%\w+]]
-; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: OpPhi
-; CHECK-NEXT: OpLoad {{%\w+}} [[var]]
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %2 "main"
-               OpExecutionMode %2 OriginUpperLeft
-               OpSource ESSL 310
-               OpMemberDecorate %_struct_3 0 Offset 0
-               OpDecorate %_struct_3 Block
-               OpDecorate %4 DescriptorSet 0
-               OpDecorate %4 Binding 0
-       %void = OpTypeVoid
-          %6 = OpTypeFunction %void
-       %bool = OpTypeBool
-      %false = OpConstantFalse %bool
-       %uint = OpTypeInt 32 0
-     %v2uint = OpTypeVector %uint 2
-  %_struct_3 = OpTypeStruct %v2uint
-%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
-          %4 = OpVariable %_ptr_Uniform__struct_3 Uniform
-     %uint_0 = OpConstant %uint 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-     %uint_2 = OpConstant %uint 2
-          %2 = OpFunction %void None %6
-         %15 = OpLabel
-               OpSelectionMerge %16 None
-               OpBranchConditional %false %17 %16
-         %17 = OpLabel
-               OpBranch %16
-         %16 = OpLabel
-         %18 = OpPhi %bool %false %15 %false %17
-               OpSelectionMerge %19 None
-               OpBranchConditional %false %20 %21
-         %20 = OpLabel
-         %22 = OpAccessChain %_ptr_Uniform_uint %4 %uint_0 %uint_0
-         %23 = OpLoad %uint %22
-               OpBranch %19
-         %21 = OpLabel
-               OpBranch %19
-         %19 = OpLabel
-               OpReturn
-               OpFunctionEnd
-)";
-
-  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
-}
-
-// TODO(greg-lunarg): Add tests to verify handling of these cases:
-//
-//    Disqualifying cases: extensions, decorations, non-logical addressing,
-//      non-structured control flow
-//    Others?
-
-}  // namespace
-}  // namespace opt
-}  // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 66e3bdd..5d41bdc 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -2868,6 +2868,295 @@
   SinglePassRunAndMatch<DeadBranchElimPass>(body, true);
 }
 
+TEST_F(DeadBranchElimTest, DontFoldBackedge) {
+  const std::string body =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%2 = OpFunction %void None %4
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranch %11
+%11 = OpLabel
+%12 = OpUndef %bool
+OpSelectionMerge %10 None
+OpBranchConditional %12 %13 %10
+%13 = OpLabel
+OpBranch %9
+%10 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpBranchConditional %false %8 %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<DeadBranchElimPass>(body, body, true);
+}
+
+TEST_F(DeadBranchElimTest, FoldBackedgeToHeader) {
+  const std::string body =
+      R"(
+; CHECK: OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpLoopMerge {{%\w+}} [[cont:%\w+]]
+; CHECK: [[cont]] = OpLabel
+; This branch may not be in the continue block, but must come after it.
+; CHECK: OpBranch [[header]]
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%2 = OpFunction %void None %4
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranch %11
+%11 = OpLabel
+%12 = OpUndef %bool
+OpSelectionMerge %10 None
+OpBranchConditional %12 %13 %10
+%13 = OpLabel
+OpBranch %9
+%10 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpBranchConditional %true %8 %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(body, true);
+}
+
+TEST_F(DeadBranchElimTest, UnreachableMergeAndContinueSameBlock) {
+  const std::string spirv = R"(
+; CHECK: OpLabel
+; CHECK: [[outer:%\w+]] = OpLabel
+; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[inner:%\w+]]
+; CHECK: [[inner]] = OpLabel
+; CHECK: OpLoopMerge [[outer_cont]] [[inner_cont:%\w+]] None
+; CHECK: [[inner_cont]] = OpLabel
+; CHECK-NEXT: OpBranch [[inner]]
+; CHECK: [[outer_cont]] = OpLabel
+; CHECK-NEXT: OpBranch [[outer]]
+; CHECK: [[outer_merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %outer_loop
+%outer_loop = OpLabel
+OpLoopMerge %outer_merge %outer_continue None
+OpBranch %inner_loop
+%inner_loop = OpLabel
+OpLoopMerge %outer_continue %inner_continue None
+OpBranch %inner_body
+%inner_body = OpLabel
+OpSelectionMerge %inner_continue None
+OpBranchConditional %true %ret %inner_continue
+%ret = OpLabel
+OpReturn
+%inner_continue = OpLabel
+OpBranchConditional %true %outer_continue %inner_loop
+%outer_continue = OpLabel
+OpBranchConditional %true %outer_merge %outer_loop
+%outer_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
+}
+
+// Fold a switch with a nested break.  The only case should be the default.
+TEST_F(DeadBranchElimTest, FoldSwitchWithNestedBreak) {
+  const std::string spirv = R"(
+; CHECK: OpSwitch %int_3 [[case_bb:%\w+]]{{[[:space:]]}}
+; CHECK: [[case_bb]] = OpLabel
+; CHECK-NEXT: OpUndef
+; CHECK-NEXT: OpSelectionMerge
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "main"
+               OpSource GLSL 450
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_3 = OpConstant %int 3
+      %int_1 = OpConstant %int 1
+       %bool = OpTypeBool
+          %2 = OpFunction %void None %4
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpSwitch %int_3 %12 3 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+         %14 = OpUndef %bool
+               OpSelectionMerge %15 None
+               OpBranchConditional %14 %16 %15
+         %16 = OpLabel
+               OpBranch %11
+         %15 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
+}
+
+TEST_F(DeadBranchElimTest, FoldBranchWithBreakToSwitch) {
+  const std::string spirv = R"(
+; CHECK: OpSelectionMerge [[sel_merge:%\w+]]
+; CHECK-NEXT: OpSwitch {{%\w+}} {{%\w+}} 3 [[bb:%\w+]]
+; CHECK: [[bb]] = OpLabel
+; CHECK-NEXT: OpBranch [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpFunctionEnd
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "main"
+               OpSource GLSL 450
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_3 = OpConstant %int 3
+      %int_1 = OpConstant %int 1
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+          %2 = OpFunction %void None %4
+         %10 = OpLabel
+  %undef_int = OpUndef %int
+               OpSelectionMerge %11 None
+               OpSwitch %undef_int %12 3 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %true %16 %15
+         %16 = OpLabel
+         %14 = OpUndef %bool
+               OpBranchConditional %14 %11 %17
+         %17 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
+}
+
+TEST_F(DeadBranchElimTest, IfInSwitch) {
+  // #version 310 es
+  //
+  // void main()
+  // {
+  //  switch(0)
+  //  {
+  //   case 0:
+  //   if(false)
+  //   {
+  //   }
+  //   else
+  //   {
+  //   }
+  //  }
+  // }
+
+  const std::string before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %int_0 %9 0 %8
+%8 = OpLabel
+OpSelectionMerge %13 None
+OpBranchConditional %false %12 %13
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<DeadBranchElimPass>(before, after, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/dead_variable_elim_test.cpp b/test/opt/dead_variable_elim_test.cpp
index fca13a8..a55ee62 100644
--- a/test/opt/dead_variable_elim_test.cpp
+++ b/test/opt/dead_variable_elim_test.cpp
@@ -217,7 +217,7 @@
 %6 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_ptr_Private_float = OpTypePointer Private %float
-%initializer = OpVariable %_ptr_Private_float Private
+%initializer = OpConstant %float 0
 %live = OpVariable %_ptr_Private_float Private %initializer
 %main = OpFunction %void None %6
 %9 = OpLabel
diff --git a/test/opt/decompose_initialized_variables_test.cpp b/test/opt/decompose_initialized_variables_test.cpp
new file mode 100644
index 0000000..cdebb3f
--- /dev/null
+++ b/test/opt/decompose_initialized_variables_test.cpp
@@ -0,0 +1,252 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DecomposeInitializedVariablesTest = PassTest<::testing::Test>;
+
+std::string single_entry_header = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "shader"
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%4 = OpConstantNull %uint
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+)";
+
+std::string GetFunctionTest(std::string body) {
+  auto result = single_entry_header;
+  result += "%_ptr_Function_uint = OpTypePointer Function %uint\n";
+  result += "%1 = OpFunction %void None %6\n";
+  result += "%8 = OpLabel\n";
+  result += body + "\n";
+  result += "OpReturn\n";
+  result += "OpFunctionEnd\n";
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionChanged) {
+  std::string input = "%9 = OpVariable %_ptr_Function_uint Function %uint_1";
+  std::string expected = R"(%9 = OpVariable %_ptr_Function_uint Function
+OpStore %9 %uint_1)";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(expected),
+      /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionUnchanged) {
+  std::string input = "%9 = OpVariable %_ptr_Function_uint Function";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(input), /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionMultipleVariables) {
+  std::string input = R"(%9 = OpVariable %_ptr_Function_uint Function %uint_1
+%10 = OpVariable %_ptr_Function_uint Function %4)";
+  std::string expected = R"(%9 = OpVariable %_ptr_Function_uint Function
+%10 = OpVariable %_ptr_Function_uint Function
+OpStore %9 %uint_1
+OpStore %10 %4)";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(expected),
+      /* skip_nop = */ false);
+}
+
+std::string GetGlobalTest(std::string storage_class, bool initialized,
+                          bool decomposed) {
+  auto result = single_entry_header;
+
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+)";
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateChanged) {
+  std::string input = GetGlobalTest("Private", true, false);
+  std::string expected = GetGlobalTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateUnchanged) {
+  std::string input = GetGlobalTest("Private", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputChanged) {
+  std::string input = GetGlobalTest("Output", true, false);
+  std::string expected = GetGlobalTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputUnchanged) {
+  std::string input = GetGlobalTest("Output", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+std::string multiple_entry_header = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "vertex"
+OpEntryPoint Fragment %2 "fragment"
+%uint = OpTypeInt 32 0
+%4 = OpConstantNull %uint
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+)";
+
+std::string GetGlobalMultipleEntryTest(std::string storage_class,
+                                       bool initialized, bool decomposed) {
+  auto result = multiple_entry_header;
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+%2 = OpFunction %void None %10
+%10 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+)";
+
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateMultipleEntryChanged) {
+  std::string input = GetGlobalMultipleEntryTest("Private", true, false);
+  std::string expected = GetGlobalMultipleEntryTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateMultipleEntryUnchanged) {
+  std::string input = GetGlobalMultipleEntryTest("Private", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputMultipleEntryChanged) {
+  std::string input = GetGlobalMultipleEntryTest("Output", true, false);
+  std::string expected = GetGlobalMultipleEntryTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputMultipleEntryUnchanged) {
+  std::string input = GetGlobalMultipleEntryTest("Output", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+std::string GetGlobalWithNonEntryPointTest(std::string storage_class,
+                                           bool initialized, bool decomposed) {
+  auto result = single_entry_header;
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+%10 = OpFunction %void None %11
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateWithNonEntryPointChanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Private", true, false);
+  std::string expected = GetGlobalWithNonEntryPointTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateWithNonEntryPointUnchanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Private", false, false);
+  //  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputWithNonEntryPointChanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Output", true, false);
+  std::string expected = GetGlobalWithNonEntryPointTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputWithNonEntryPointUnchanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Output", false, false);
+  //  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/decoration_manager_test.cpp b/test/opt/decoration_manager_test.cpp
index f85ff6a..3eb3ef5 100644
--- a/test/opt/decoration_manager_test.cpp
+++ b/test/opt/decoration_manager_test.cpp
@@ -709,8 +709,8 @@
   EXPECT_THAT(GetErrorMessage(), "");
 
   std::string expected_decorations =
-      R"(OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
-OpDecorateId %5 HlslCounterBufferGOOGLE %2
+      R"(OpDecorateString %5 UserSemantic "blah"
+OpDecorateId %5 CounterBuffer %2
 OpDecorate %5 Aliased
 )";
   EXPECT_THAT(ToText(decorations), expected_decorations);
@@ -720,11 +720,11 @@
 OpExtension "SPV_GOOGLE_hlsl_functionality1"
 OpExtension "SPV_GOOGLE_decorate_string"
 OpMemoryModel Logical GLSL450
-OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah"
-OpDecorateId %1 HlslCounterBufferGOOGLE %2
+OpDecorateString %1 UserSemantic "blah"
+OpDecorateId %1 CounterBuffer %2
 OpDecorate %1 Aliased
-OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
-OpDecorateId %5 HlslCounterBufferGOOGLE %2
+OpDecorateString %5 UserSemantic "blah"
+OpDecorateId %5 CounterBuffer %2
 OpDecorate %5 Aliased
 %3 = OpTypeInt 32 0
 %4 = OpTypePointer Uniform %3
@@ -1277,6 +1277,232 @@
   EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
 }
 
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+OpDecorate %1 Constant
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+OpDecorate %1 Constant
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate5) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate6) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorate7) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+OpDecorate %1 Invariant
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%2      = OpVariable %u32 Uniform
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpMemberDecorate1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %1 0 Offset 4
+OpMemberDecorate %2 0 Offset 0
+OpMemberDecorate %2 0 Offset 4
+%u32    = OpTypeInt 32 0
+%1      = OpTypeStruct %u32 %u32 %u32
+%2      = OpTypeStruct %u32 %u32 %u32
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpMemberDecorate2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpMemberDecorate %2 0 Offset 4
+%u32    = OpTypeInt 32 0
+%1      = OpTypeStruct %u32 %u32 %u32
+%2      = OpTypeStruct %u32 %u32 %u32
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorateId1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %2
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%3      = OpVariable %u32 Uniform
+%2      = OpSpecConstant %u32 0
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 3u));
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(3u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorateId2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %2
+OpDecorateId %3 AlignmentId %4
+%u32    = OpTypeInt 32 0
+%1      = OpVariable %u32 Uniform
+%3      = OpVariable %u32 Uniform
+%2      = OpSpecConstant %u32 0
+%4      = OpSpecConstant %u32 1
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 3u));
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(3u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorateString1) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateString %1 HlslSemanticGOOGLE "hello"
+OpDecorateString %2 HlslSemanticGOOGLE "world"
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
+
+TEST_F(DecorationManagerTest, SubSetTestOpDecorateString2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateString %1 HlslSemanticGOOGLE "hello"
+)";
+  DecorationManager* decoManager = GetDecorationManager(spirv);
+  EXPECT_THAT(GetErrorMessage(), "");
+  EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u));
+  EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u));
+}
 }  // namespace
 }  // namespace analysis
 }  // namespace opt
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp
index 3b856ce..cfdad74 100644
--- a/test/opt/def_use_test.cpp
+++ b/test/opt/def_use_test.cpp
@@ -206,7 +206,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, ParseDefUseTest,
     ::testing::ValuesIn(std::vector<ParseDefUseCase>{
         {"", {{}, {}}},                              // no instruction
@@ -629,7 +629,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, ReplaceUseTest,
     ::testing::ValuesIn(std::vector<ReplaceUseCase>{
       { // no use, no replace request
@@ -981,7 +981,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, KillDefTest,
     ::testing::ValuesIn(std::vector<KillDefCase>{
       { // no def, no use, no kill
@@ -1343,7 +1343,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, AnalyzeInstDefUseTest,
     ::testing::ValuesIn(std::vector<AnalyzeInstDefUseTestCase>{
       { // A type declaring instruction.
@@ -1464,7 +1464,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, KillInstTest,
     ::testing::ValuesIn(std::vector<KillInstTestCase>{
       // Kill id defining instructions.
@@ -1588,7 +1588,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, GetAnnotationsTest,
     ::testing::ValuesIn(std::vector<GetAnnotationsTestCase>{
       // empty
diff --git a/test/opt/eliminate_dead_const_test.cpp b/test/opt/eliminate_dead_const_test.cpp
index 7fac866..59f06f9 100644
--- a/test/opt/eliminate_dead_const_test.cpp
+++ b/test/opt/eliminate_dead_const_test.cpp
@@ -197,7 +197,7 @@
       assembly_with_dead_const, expected, /*  skip_nop = */ true);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -265,7 +265,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -358,7 +358,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StructTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -485,7 +485,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeSpecConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -522,7 +522,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeSpecConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -617,7 +617,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SpecConstantOp, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -768,7 +768,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp
new file mode 100644
index 0000000..7d5db7d
--- /dev/null
+++ b/test/opt/eliminate_dead_member_test.cpp
@@ -0,0 +1,1087 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using EliminateDeadMemberTest = opt::PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadMemberTest, RemoveMember1) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberWithGroupDecorations) {
+  // Test that the member "y" is removed.
+  // Update OpGroupMemberDecorate for %type__Globals member 1 and 2.
+  // Update OpAccessChain for access to %type__Globals member 2.
+  const std::string text = R"(
+; CHECK: OpDecorate [[gr1:%\w+]] Offset 0
+; CHECK: OpDecorate [[gr2:%\w+]] Offset 4
+; CHECK: OpDecorate [[gr3:%\w+]] Offset 8
+; CHECK: [[gr1]] = OpDecorationGroup
+; CHECK: [[gr2]] = OpDecorationGroup
+; CHECK: [[gr3]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[gr1]] %type__Globals 0
+; CHECK-NOT: OpGroupMemberDecorate [[gr2]]
+; CHECK: OpGroupMemberDecorate [[gr3]] %type__Globals 1
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpName %_Globals "$Globals"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpDecorate %gr1 Offset 0
+               OpDecorate %gr2 Offset 4
+               OpDecorate %gr3 Offset 8
+               OpDecorate %type__Globals Block
+        %gr1 = OpDecorationGroup
+        %gr2 = OpDecorationGroup
+        %gr3 = OpDecorationGroup
+               OpGroupMemberDecorate %gr1 %type__Globals 0
+               OpGroupMemberDecorate %gr2 %type__Globals 1
+               OpGroupMemberDecorate %gr3 %type__Globals 2
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // Skipping validation because of a bug in the validator.  See issue #2376.
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, false);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateConstant) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpConstantComposite %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+         %13 = OpConstantComposite %type__Globals %float_0 %float_1 %float_2
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateCompositeConstruct) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpCompositeConstruct %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %13 = OpCompositeConstruct %type__Globals %float_0 %float_1 %float_2
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract1) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Remove the OpCompositeInsert instruction since the member being inserted is
+  // dead.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: OpCompositeExtract %float [[ld]] 0
+; CHECK-NOT: OpCompositeInsert
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 2
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract2) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Update the OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract3) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: %type__Globals = OpTypeStruct [[struct]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 24
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+%type__Globals = OpTypeStruct %float %_struct_6 %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract4) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 1 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 1 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 80
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %array ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0                         ; 32-bit int, sign-less
+     %uint_4 = OpConstant %uint 4
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+  %array = OpTypeArray %_struct_6 %uint_4
+%type__Globals = OpTypeStruct %float %array %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateArrayLength) {
+  // Test that the members "x" and "y" are removed.
+  // Member "z" is live because of the OpArrayLength instruction.
+  // Update the OpArrayLength instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %_runtimearr_float
+; CHECK: OpArrayLength %uint %_Globals 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%_runtimearr_float = OpTypeRuntimeArray %float
+%type__Globals = OpTypeStruct %float %float %_runtimearr_float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+         %12 = OpArrayLength %uint %_Globals 2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpStore) {
+  // Test that all members are kept because of an OpStore.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpStore %_Globals2 %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemory) {
+  // Test that all members are kept because of an OpCopyMemory.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemory %_Globals2 %_Globals
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemorySized) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemorySized %_Globals2 %_Globals %uint_20
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpReturnValue) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %type__Globals
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %type__Globals None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpReturnValue %11
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberAccessChainWithArrays) {
+  // Leave only 1 member in each of the structs.
+  // Update OpMemberName, OpMemberDecorate, and OpAccessChain.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[undef:%\w+]] = OpUndef %uint
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals [[undef]] %uint_0 [[undef]] %uint_0
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 48
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpMemberDecorate %_struct_4 1 Offset 4
+               OpDecorate %_arr__struct_4_uint_2 ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+  %_struct_4 = OpTypeStruct %float %float
+%_arr__struct_4_uint_2 = OpTypeArray %_struct_4 %uint_2
+%type__Globals = OpTypeStruct %float %_arr__struct_4_uint_2 %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpUndef %uint
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %18 %uint_1 %18 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInboundsAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInBoundsPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, DontRemoveModfStructResultTypeMembers) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%ModfStructType = OpTypeStruct %float %float
+%main = OpFunction %void None %21
+         %22 = OpLabel
+         %23 = OpUndef %float
+         %24 = OpExtInst %ModfStructType %1 ModfStruct %23
+         %25 = OpCompositeExtract %float %24 1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, DontChangeInputStructs) {
+  // The input for a shader has to match the type of the output from the
+  // previous shader in the pipeline.  Because of that, we cannot change the
+  // types of input variables.
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %input_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%in_var_type = OpTypeStruct %float %float
+%in_ptr_type = OpTypePointer Input %in_var_type
+%input_var = OpVariable %in_ptr_type Input
+%main = OpFunction %void None %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, DontChangeOutputStructs) {
+  // The output for a shader has to match the type of the output from the
+  // previous shader in the pipeline.  Because of that, we cannot change the
+  // types of output variables.
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %output_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%out_var_type = OpTypeStruct %float %float
+%out_ptr_type = OpTypePointer Output %out_var_type
+%output_var = OpVariable %out_ptr_type Output
+%main = OpFunction %void None %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+}  // namespace
diff --git a/test/opt/fix_storage_class_test.cpp b/test/opt/fix_storage_class_test.cpp
new file mode 100644
index 0000000..4c8504a
--- /dev/null
+++ b/test/opt/fix_storage_class_test.cpp
@@ -0,0 +1,840 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FixStorageClassTest = PassTest<::testing::Test>;
+
+TEST_F(FixStorageClassTest, FixAccessChain) {
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %8 DescriptorSet 0
+               OpDecorate %8 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+  %_struct_5 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_5 = OpTypePointer Workgroup %_struct_5
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %30 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+          %6 = OpVariable %_ptr_Workgroup__struct_5 Workgroup
+          %8 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %30
+         %38 = OpLabel
+         %44 = OpLoad %v3uint %gl_LocalInvocationID
+         %50 = OpAccessChain %_ptr_Function_float %6 %int_0 %int_0 %int_0
+         %51 = OpLoad %float %50
+         %52 = OpFMul %float %float_2 %51
+               OpStore %50 %52
+         %55 = OpLoad %float %50
+         %59 = OpCompositeExtract %uint %44 0
+         %60 = OpAccessChain %_ptr_Uniform_float %8 %int_0 %59
+               OpStore %60 %55
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixLinkedAccessChain) {
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup__arr_float_uint_10
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %27 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %23
+         %28 = OpLabel
+         %29 = OpLoad %v3uint %gl_LocalInvocationID
+         %30 = OpAccessChain %_ptr_Function__arr_float_uint_10 %27 %int_0 %int_0
+         %31 = OpAccessChain %_ptr_Function_float %30 %int_0
+         %32 = OpLoad %float %31
+         %33 = OpFMul %float %float_2 %32
+               OpStore %31 %33
+         %34 = OpLoad %float %31
+         %35 = OpCompositeExtract %uint %29 0
+         %36 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %35
+               OpStore %36 %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixCopyObject) {
+  const std::string text = R"(
+; CHECK: OpCopyObject %_ptr_Workgroup__struct_17
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %8 DescriptorSet 0
+               OpDecorate %8 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+  %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %30 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+          %6 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %8 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %30
+         %38 = OpLabel
+         %44 = OpLoad %v3uint %gl_LocalInvocationID
+         %cp = OpCopyObject %_ptr_Function__struct_17 %6
+         %50 = OpAccessChain %_ptr_Function_float %cp %int_0 %int_0 %int_0
+         %51 = OpLoad %float %50
+         %52 = OpFMul %float %float_2 %51
+               OpStore %50 %52
+         %55 = OpLoad %float %50
+         %59 = OpCompositeExtract %uint %44 0
+         %60 = OpAccessChain %_ptr_Uniform_float %8 %int_0 %59
+               OpStore %60 %55
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixPhiInSelMerge) {
+  const std::string text = R"(
+; CHECK: OpPhi %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %true %32 %31
+         %32 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %33 = OpPhi %_ptr_Function__struct_19 %28 %30 %29 %32
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixPhiInLoop) {
+  const std::string text = R"(
+; CHECK: OpPhi %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %true %32 %31
+         %32 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %33 = OpPhi %_ptr_Function__struct_19 %28 %30 %29 %32
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, DontChangeFunctionCalls) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "testMain"
+OpExecutionMode %1 LocalSize 8 8 1
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%9 = OpTypeFunction %_ptr_Uniform_int %_ptr_Function_int
+%10 = OpVariable %_ptr_Workgroup_int Workgroup
+%2 = OpVariable %_ptr_Uniform_int Uniform
+%1 = OpFunction %void None %8
+%11 = OpLabel
+%12 = OpFunctionCall %_ptr_Uniform_int %13 %10
+OpReturn
+OpFunctionEnd
+%13 = OpFunction %_ptr_Uniform_int None %9
+%14 = OpFunctionParameter %_ptr_Function_int
+%15 = OpLabel
+OpReturnValue %2
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<FixStorageClass>(text, text, false, false);
+}
+
+TEST_F(FixStorageClassTest, FixSelect) {
+  const std::string text = R"(
+; CHECK: OpSelect %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+         %33 = OpSelect %_ptr_Function__struct_19 %true %28 %29
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, BitCast) {
+  const std::string text = R"(OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%_ptr_Output_void = OpTypePointer Output %void
+%_ptr_Private__ptr_Output_void = OpTypePointer Private %_ptr_Output_void
+%6 = OpVariable %_ptr_Private__ptr_Output_void Private
+%1 = OpFunction %void Inline %3
+%7 = OpLabel
+%8 = OpBitcast %_ptr_Output_void %6
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<FixStorageClass>(text, text, false);
+}
+
+TEST_F(FixStorageClassTest, FixLinkedAccessChain2) {
+  // This case is similar to FixLinkedAccessChain.  The difference is that the
+  // first OpAccessChain instruction starts as workgroup storage class.  Only
+  // the second one needs to change.
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup__arr_float_uint_10
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Workgroup__arr_float_uint_10 = OpTypePointer Workgroup %_arr_float_uint_10
+%_ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %27 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %23
+         %28 = OpLabel
+         %29 = OpLoad %v3uint %gl_LocalInvocationID
+         %30 = OpAccessChain %_ptr_Workgroup__arr_float_uint_10 %27 %int_0 %int_0
+         %31 = OpAccessChain %_ptr_Function_float %30 %int_0
+         %32 = OpLoad %float %31
+         %33 = OpFMul %float %float_2 %32
+               OpStore %31 %33
+         %34 = OpLoad %float %31
+         %35 = OpCompositeExtract %uint %29 0
+         %36 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %35
+               OpStore %36 %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+using FixTypeTest = PassTest<::testing::Test>;
+
+TEST_F(FixTypeTest, FixAccessChain) {
+  const std::string text = R"(
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_T [[ac1]] %int_0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Function_T_0 %27 %int_0
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixLoad) {
+  const std::string text = R"(
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_T [[ac1]] %int_0
+; CHECK: [[ld:%\w+]] = OpLoad %T [[ac2]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Uniform_T %27 %int_0
+         %29 = OpLoad %T_0 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixStore) {
+  const std::string text = R"(
+; CHECK: [[ld:%\w+]] = OpLoad %T
+; CHECK: OpStore
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Uniform_T %27 %int_0
+         %29 = OpLoad %T %28
+               OpStore %25 %29
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixSelect) {
+  const std::string text = R"(
+; CHECK: OpSelect %_ptr_Uniform__struct_3
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+  %_struct_3 = OpTypeStruct %uint
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+ %_struct_12 = OpTypeStruct %uint
+%_ptr_Function__struct_12 = OpTypePointer Function %_struct_12
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+       %bool = OpTypeBool
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %11
+         %17 = OpLabel
+         %18 = OpAccessChain %_ptr_Uniform_uint %2 %uint_0 %uint_0 %uint_0
+         %19 = OpLoad %uint %18
+         %20 = OpSGreaterThan %bool %19 %uint_0
+         %21 = OpAccessChain %_ptr_Uniform__struct_3 %2 %uint_0 %uint_0
+         %22 = OpAccessChain %_ptr_Uniform__struct_3 %2 %uint_0 %uint_1
+         %23 = OpSelect %_ptr_Function__struct_12 %20 %21 %22
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixPhiInLoop) {
+  const std::string text = R"(
+; CHECK: [[ac_init:%\w+]] = OpAccessChain %_ptr_Uniform__struct_3
+; CHECK: [[ac_phi:%\w+]] = OpPhi %_ptr_Uniform__struct_3 [[ac_init]] {{%\w+}} [[ac_update:%\w+]] {{%\w+}}
+; CHECK: [[ac_update]] = OpPtrAccessChain %_ptr_Uniform__struct_3 [[ac_phi]] %int_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+  %_struct_3 = OpTypeStruct %int
+  %_struct_9 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %12 = OpTypeFunction %void
+       %bool = OpTypeBool
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+%_ptr_Function__struct_9 = OpTypePointer Function %_struct_9
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %12
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %int_0
+               OpBranch %18
+         %18 = OpLabel
+         %20 = OpPhi %_ptr_Function__struct_9 %17 %16 %21 %22
+         %23 = OpUndef %bool
+               OpLoopMerge %24 %22 None
+               OpBranchConditional %23 %22 %24
+         %22 = OpLabel
+         %21 = OpPtrAccessChain %_ptr_Function__struct_9 %20 %int_1
+               OpBranch %18
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/flatten_decoration_test.cpp b/test/opt/flatten_decoration_test.cpp
index fcf2341..d8d8867 100644
--- a/test/opt/flatten_decoration_test.cpp
+++ b/test/opt/flatten_decoration_test.cpp
@@ -81,158 +81,158 @@
   SinglePassRunAndCheck<FlattenDecorationPass>(before, after, false, true);
 }
 
-INSTANTIATE_TEST_CASE_P(NoUses, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // No OpDecorationGroup
-                            {"", ""},
+INSTANTIATE_TEST_SUITE_P(NoUses, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // No OpDecorationGroup
+                             {"", ""},
 
-                            // OpDecorationGroup without any uses, and
-                            // no OpName.
-                            {"%group = OpDecorationGroup\n", ""},
+                             // OpDecorationGroup without any uses, and
+                             // no OpName.
+                             {"%group = OpDecorationGroup\n", ""},
 
-                            // OpDecorationGroup without any uses, and
-                            // with OpName targeting it. Proves you must
-                            // remove the names as well.
-                            {"OpName %group \"group\"\n"
-                             "%group = OpDecorationGroup\n",
-                             ""},
+                             // OpDecorationGroup without any uses, and
+                             // with OpName targeting it. Proves you must
+                             // remove the names as well.
+                             {"OpName %group \"group\"\n"
+                              "%group = OpDecorationGroup\n",
+                              ""},
 
-                            // OpDecorationGroup with decorations that
-                            // target it, but no uses in OpGroupDecorate
-                            // or OpGroupMemberDecorate instructions.
-                            {"OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n",
-                             ""},
-                        }), );
+                             // OpDecorationGroup with decorations that
+                             // target it, but no uses in OpGroupDecorate
+                             // or OpGroupMemberDecorate instructions.
+                             {"OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n",
+                              ""},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(OpGroupDecorate, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // One OpGroupDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Multiple OpGroupDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %value\n"
-                             "OpGroupDecorate %group %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %value Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %value NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Two group decorations, interleaved
-                            {"OpName %group0 \"group0\"\n"
-                             "OpName %group1 \"group1\"\n"
-                             "OpDecorate %group0 Flat\n"
-                             "OpDecorate %group1 NoPerspective\n"
-                             "%group0 = OpDecorationGroup\n"
-                             "%group1 = OpDecorationGroup\n"
-                             "OpGroupDecorate %group0 %hue %value\n"
-                             "OpGroupDecorate %group1 %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %value Flat\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Decoration with operands
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Location 42\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Location 42\n"
-                             "OpDecorate %saturation Location 42\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(OpGroupDecorate, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // One OpGroupDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Multiple OpGroupDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %value\n"
+                              "OpGroupDecorate %group %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %value Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %value NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Two group decorations, interleaved
+                             {"OpName %group0 \"group0\"\n"
+                              "OpName %group1 \"group1\"\n"
+                              "OpDecorate %group0 Flat\n"
+                              "OpDecorate %group1 NoPerspective\n"
+                              "%group0 = OpDecorationGroup\n"
+                              "%group1 = OpDecorationGroup\n"
+                              "OpGroupDecorate %group0 %hue %value\n"
+                              "OpGroupDecorate %group1 %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %value Flat\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Decoration with operands
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Location 42\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Location 42\n"
+                              "OpDecorate %saturation Location 42\n"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(OpGroupMemberDecorate, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // One OpGroupMemberDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group Offset 16\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 1\n",
-                             "OpMemberDecorate %Point 1 Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 16\n"},
-                            // Multiple OpGroupMemberDecorate using the same
-                            // decoration group.
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "OpDecorate %group Offset 8\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 2\n"
-                             "OpGroupMemberDecorate %group %Camera 1\n",
-                             "OpMemberDecorate %Point 2 Flat\n"
-                             "OpMemberDecorate %Camera 1 Flat\n"
-                             "OpMemberDecorate %Point 2 NoPerspective\n"
-                             "OpMemberDecorate %Camera 1 NoPerspective\n"
-                             "OpMemberDecorate %Point 2 Offset 8\n"
-                             "OpMemberDecorate %Camera 1 Offset 8\n"},
-                            // Two groups of member decorations, interleaved.
-                            // Decoration is with and without operands.
-                            {"OpName %group0 \"group0\"\n"
-                             "OpName %group1 \"group1\"\n"
-                             "OpDecorate %group0 Flat\n"
-                             "OpDecorate %group0 Offset 8\n"
-                             "OpDecorate %group1 NoPerspective\n"
-                             "OpDecorate %group1 Offset 16\n"
-                             "%group0 = OpDecorationGroup\n"
-                             "%group1 = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group0 %Point 0\n"
-                             "OpGroupMemberDecorate %group1 %Point 2\n",
-                             "OpMemberDecorate %Point 0 Flat\n"
-                             "OpMemberDecorate %Point 0 Offset 8\n"
-                             "OpMemberDecorate %Point 2 NoPerspective\n"
-                             "OpMemberDecorate %Point 2 Offset 16\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(OpGroupMemberDecorate, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // One OpGroupMemberDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group Offset 16\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 1\n",
+                              "OpMemberDecorate %Point 1 Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 16\n"},
+                             // Multiple OpGroupMemberDecorate using the same
+                             // decoration group.
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "OpDecorate %group Offset 8\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 2\n"
+                              "OpGroupMemberDecorate %group %Camera 1\n",
+                              "OpMemberDecorate %Point 2 Flat\n"
+                              "OpMemberDecorate %Camera 1 Flat\n"
+                              "OpMemberDecorate %Point 2 NoPerspective\n"
+                              "OpMemberDecorate %Camera 1 NoPerspective\n"
+                              "OpMemberDecorate %Point 2 Offset 8\n"
+                              "OpMemberDecorate %Camera 1 Offset 8\n"},
+                             // Two groups of member decorations, interleaved.
+                             // Decoration is with and without operands.
+                             {"OpName %group0 \"group0\"\n"
+                              "OpName %group1 \"group1\"\n"
+                              "OpDecorate %group0 Flat\n"
+                              "OpDecorate %group0 Offset 8\n"
+                              "OpDecorate %group1 NoPerspective\n"
+                              "OpDecorate %group1 Offset 16\n"
+                              "%group0 = OpDecorationGroup\n"
+                              "%group1 = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group0 %Point 0\n"
+                              "OpGroupMemberDecorate %group1 %Point 2\n",
+                              "OpMemberDecorate %Point 0 Flat\n"
+                              "OpMemberDecorate %Point 0 Offset 8\n"
+                              "OpMemberDecorate %Point 2 NoPerspective\n"
+                              "OpMemberDecorate %Point 2 Offset 16\n"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(UnrelatedDecorations, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // A non-group non-member decoration is untouched.
-                            {"OpDecorate %hue Centroid\n"
-                             "OpDecorate %saturation Flat\n",
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %saturation Flat\n"},
-                            // A non-group member decoration is untouched.
-                            {"OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "OpMemberDecorate %Point 1 Flat\n",
-                             "OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "OpMemberDecorate %Point 1 Flat\n"},
-                            // A non-group non-member decoration survives any
-                            // replacement of group decorations.
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // A non-group member decoration survives any
-                            // replacement of group decorations.
-                            {"OpDecorate %group Offset 0\n"
-                             "OpDecorate %group Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 0\n",
-                             "OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 0 Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(UnrelatedDecorations, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // A non-group non-member decoration is untouched.
+                             {"OpDecorate %hue Centroid\n"
+                              "OpDecorate %saturation Flat\n",
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %saturation Flat\n"},
+                             // A non-group member decoration is untouched.
+                             {"OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "OpMemberDecorate %Point 1 Flat\n",
+                              "OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "OpMemberDecorate %Point 1 Flat\n"},
+                             // A non-group non-member decoration survives any
+                             // replacement of group decorations.
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // A non-group member decoration survives any
+                             // replacement of group decorations.
+                             {"OpDecorate %group Offset 0\n"
+                              "OpDecorate %group Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 0\n",
+                              "OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 0 Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"},
+                         }));
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/fold_spec_const_op_composite_test.cpp b/test/opt/fold_spec_const_op_composite_test.cpp
index 8ecfd5c..c96dc8c 100644
--- a/test/opt/fold_spec_const_op_composite_test.cpp
+++ b/test/opt/fold_spec_const_op_composite_test.cpp
@@ -224,7 +224,7 @@
 
 // Tests that OpSpecConstantComposite opcodes are replace with
 // OpConstantComposite correctly.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Composite, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -325,12 +325,25 @@
                 "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef",
                 "%outer = OpSpecConstantComposite %outer_struct %inner %signed_one",
               },
+            },
+            // Fold an QuantizetoF16 instruction
+            {
+              // original
+              {
+                "%float_1 = OpConstant %float 1",
+                "%quant_float = OpSpecConstantOp %float QuantizeToF16 %float_1",
+              },
+              // expected
+              {
+                "%float_1 = OpConstant %float 1",
+                "%quant_float = OpConstant %float 1",
+              },
             }
         // clang-format on
     })));
 
 // Tests for operations that resulting in different types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Cast, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(
         std::vector<FoldSpecConstantOpAndCompositePassTestCase>({
@@ -567,7 +580,7 @@
 
 // Tests about boolean scalar logical operations and comparison operations with
 // scalar int/uint type.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Logical, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -636,7 +649,7 @@
     })));
 
 // Tests about arithmetic operations for scalar int and uint types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarArithmetic, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -821,7 +834,7 @@
     })));
 
 // Tests about arithmetic operations for vector int and uint types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorArithmetic, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1043,7 +1056,7 @@
     })));
 
 // Tests for SpecConstantOp CompositeExtract instruction
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CompositeExtract, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1135,14 +1148,14 @@
                 "%outer = OpConstantComposite %outer_struct %inner %signed_one",
                 "%extract_inner = OpSpecConstantOp %inner_struct CompositeExtract %outer 0",
                 "%extract_int = OpSpecConstantOp %int CompositeExtract %outer 1",
-                "%extract_inner_float = OpSpecConstantOp %int CompositeExtract %outer 0 2",
+                "%extract_inner_float = OpSpecConstantOp %float CompositeExtract %outer 0 2",
               },
               // expected
               {
                 "%float_1 = OpConstant %float 1",
                 "%inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1",
                 "%outer = OpConstantComposite %outer_struct %inner %signed_one",
-                "%extract_inner = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+                "%extract_inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1",
                 "%extract_int = OpConstant %int 1",
                 "%extract_inner_float = OpConstant %float 1",
               },
@@ -1217,7 +1230,7 @@
     })));
 
 // Tests the swizzle operations for spec const vectors.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorShuffle, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1256,13 +1269,9 @@
               },
               // expected
               {
-                "%60 = OpConstantNull %int",
                 "%a = OpConstantComposite %v2int %signed_null %signed_null",
-                "%62 = OpConstantNull %int",
                 "%b = OpConstantComposite %v2int %signed_zero %signed_one",
-                "%64 = OpConstantNull %int",
                 "%c = OpConstantComposite %v2int %signed_three %signed_null",
-                "%66 = OpConstantNull %int",
                 "%d = OpConstantComposite %v2int %signed_null %signed_null",
               }
             },
@@ -1310,7 +1319,7 @@
     })));
 
 // Test with long use-def chain.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 487c18a..3ea3204 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -142,6 +142,7 @@
 %v4double = OpTypeVector %double 4
 %v2float = OpTypeVector %float 2
 %v2double = OpTypeVector %double 2
+%v2half = OpTypeVector %half 2
 %v2bool = OpTypeVector %bool 2
 %struct_v2int_int_int = OpTypeStruct %v2int %int %int
 %_ptr_int = OpTypePointer Function %int
@@ -205,7 +206,14 @@
 %float_2 = OpConstant %float 2
 %float_3 = OpConstant %float 3
 %float_4 = OpConstant %float 4
+%float_2049 = OpConstant %float 2049
+%float_n2049 = OpConstant %float -2049
 %float_0p5 = OpConstant %float 0.5
+%float_pi = OpConstant %float 1.5555
+%float_1e16 = OpConstant %float 1e16
+%float_n1e16 = OpConstant %float -1e16
+%float_1en16 = OpConstant %float 1e-16
+%float_n1en16 = OpConstant %float -1e-16
 %v2float_0_0 = OpConstantComposite %v2float %float_0 %float_0
 %v2float_2_2 = OpConstantComposite %v2float %float_2 %float_2
 %v2float_2_3 = OpConstantComposite %v2float %float_2 %float_3
@@ -231,6 +239,7 @@
 %v2double_null = OpConstantNull %v2double
 %108 = OpConstant %half 0
 %half_1 = OpConstant %half 1
+%half_0_1 = OpConstantComposite %v2half %108 %half_1
 %106 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %v4float_0_0_0_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
@@ -265,7 +274,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntegerInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 0*n
   InstructionFoldingCase<uint32_t>(
@@ -590,7 +599,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntVectorInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntVectorInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold 0*n
     InstructionFoldingCase<std::vector<uint32_t>>(
@@ -667,7 +676,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold true || n
   InstructionFoldingCase<bool>(
@@ -871,7 +880,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FClampAndCmpLHS, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FClampAndCmpLHS, BooleanInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold 0.0 > clamp(n, 0.0, 1.0)
     InstructionFoldingCase<bool>(
@@ -1051,7 +1060,7 @@
         2, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(FClampAndCmpRHS, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FClampAndCmpRHS, BooleanInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold clamp(n, 0.0, 1.0) > 1.0
     InstructionFoldingCase<bool>(
@@ -1271,7 +1280,11 @@
         const_mrg->GetConstantFromInst(inst)->AsFloatConstant();
     EXPECT_NE(result, nullptr);
     if (result != nullptr) {
-      EXPECT_EQ(result->GetFloatValue(), tc.expected_result);
+      if (!std::isnan(tc.expected_result)) {
+        EXPECT_EQ(result->GetFloatValue(), tc.expected_result);
+      } else {
+        EXPECT_TRUE(std::isnan(result->GetFloatValue()));
+      }
     }
   }
 }
@@ -1281,7 +1294,7 @@
 // specification.
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 2.0 - 1.0
     InstructionFoldingCase<float>(
@@ -1386,7 +1399,89 @@
             "%2 = OpFNegate %float %float_2\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        2, -2)
+        2, -2),
+    // Test case 12: QuantizeToF16 1.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 1.0),
+    // Test case 13: QuantizeToF16 positive non exact
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_2049\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 2048),
+    // Test case 14: QuantizeToF16 negative non exact
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_n2049\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, -2048),
+    // Test case 15: QuantizeToF16 large positive
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_1e16\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::infinity()),
+    // Test case 16: QuantizeToF16 large negative
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_n1e16\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, -std::numeric_limits<float>::infinity()),
+    // Test case 17: QuantizeToF16 small positive
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_1en16\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 18: QuantizeToF16 small negative
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_n1en16\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0.0),
+    // Test case 19: QuantizeToF16 nan
+    InstructionFoldingCase<float>(
+        HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpQuantizeToF16 %float %float_nan\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::quiet_NaN()),
+    // Test case 20: QuantizeToF16 inf
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_1 %float_0\n" +
+            "%3 = OpQuantizeToF16 %float %3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::infinity()),
+    // Test case 21: QuantizeToF16 -inf
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_n1 %float_0\n" +
+            "%3 = OpQuantizeToF16 %float %3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, -std::numeric_limits<float>::infinity())
 ));
 // clang-format on
 
@@ -1424,7 +1519,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 2.0 - 1.0
     InstructionFoldingCase<double>(
@@ -1534,7 +1629,7 @@
 // clang-format on
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1666,7 +1761,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1798,7 +1893,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1930,7 +2025,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -2062,7 +2157,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold NaN == 0 (ord)
   InstructionFoldingCase<bool>(
@@ -2098,7 +2193,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold NaN == 0 (ord)
   InstructionFoldingCase<bool>(
@@ -2180,7 +2275,7 @@
   }
 }
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTestWithMap,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntegerInstructionFoldingTestWithMap,
   ::testing::Values(
       // Test case 0: fold %3 = 0; %3 * n
       InstructionFoldingCaseWithMap<uint32_t>(
@@ -2230,7 +2325,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTestWithMap,
+INSTANTIATE_TEST_SUITE_P(TestCase, BooleanInstructionFoldingTestWithMap,
   ::testing::Values(
       // Test case 0: fold %3 = true; %3 || n
       InstructionFoldingCaseWithMap<bool>(
@@ -2280,7 +2375,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n * m
     InstructionFoldingCase<uint32_t>(
@@ -2740,7 +2835,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold Insert feeding extract
     InstructionFoldingCase<uint32_t>(
@@ -2888,7 +2983,7 @@
         4, INT_7_ID)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold Extracts feeding construct
     InstructionFoldingCase<uint32_t>(
@@ -2951,7 +3046,7 @@
         2, VEC2_0_ID)
 ));
 
-INSTANTIATE_TEST_CASE_P(PhiFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(PhiFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: Fold phi with the same values for all edges.
   InstructionFoldingCase<uint32_t>(
@@ -2993,7 +3088,7 @@
       2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1.0
     InstructionFoldingCase<uint32_t>(
@@ -3171,7 +3266,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         3, 2),
-    // Test case 15: Fold vector fsub with null
+    // Test case 17: Fold vector fsub with null
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3181,7 +3276,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         3, 2),
-    // Test case 16: Fold 0.0(half) * n
+    // Test case 18: Fold 0.0(half) * n
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3191,7 +3286,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, HALF_0_ID),
-    // Test case 17: Don't fold 1.0(half) * n
+    // Test case 19: Don't fold 1.0(half) * n
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3201,17 +3296,33 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 0),
-    // Test case 18: Don't fold 1.0 * 1.0 (half)
+    // Test case 20: Don't fold 1.0 * 1.0 (half)
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
             "%2 = OpFMul %half %half_1 %half_1\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
+        2, 0),
+    // Test case 21: Don't fold (0.0, 1.0) * (0.0, 1.0) (half)
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFMul %v2half %half_0_1 %half_0_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0),
+    // Test case 22: Don't fold (0.0, 1.0) dotp (0.0, 1.0) (half)
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpDot %half %half_0_1 %half_0_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1.0
     InstructionFoldingCase<uint32_t>(
@@ -3371,7 +3482,7 @@
         2, 4)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
     InstructionFoldingCase<uint32_t>(
@@ -3405,7 +3516,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
     InstructionFoldingCase<uint32_t>(
@@ -3439,7 +3550,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1
     InstructionFoldingCase<uint32_t>(
@@ -3523,7 +3634,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(ClampAndCmpLHS, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ClampAndCmpLHS, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Don't Fold 0.0 < clamp(-1, 1)
     InstructionFoldingCase<uint32_t>(
@@ -3659,7 +3770,7 @@
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(ClampAndCmpRHS, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ClampAndCmpRHS, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Don't Fold clamp(-1, 1) < 0.0
     InstructionFoldingCase<uint32_t>(
@@ -3795,7 +3906,7 @@
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Fold int(3.0)
     InstructionFoldingCase<uint32_t>(
@@ -3815,7 +3926,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Fold float(3)
     InstructionFoldingCase<float>(
@@ -3870,7 +3981,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold 1.0 - n
     InstructionFoldingCase<uint32_t>(
@@ -3914,7 +4025,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold 1.0 - n
     InstructionFoldingCase<uint32_t>(
@@ -3981,7 +4092,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(RedundantIntegerMatching, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(RedundantIntegerMatching, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 0 + n (change sign)
     InstructionFoldingCase<bool>(
@@ -4011,7 +4122,7 @@
         2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeNegateTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeNegateTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: fold consecutive fnegate
   // -(-x) = x
@@ -4033,7 +4144,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4050,7 +4161,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4084,7 +4195,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFDiv [[float]] [[float_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4101,7 +4212,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4118,7 +4229,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4200,7 +4311,7 @@
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
       "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
       "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4218,7 +4329,7 @@
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
       "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
       "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4269,7 +4380,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
-      "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" +
+      "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
       "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -4319,11 +4430,11 @@
     InstructionFoldingCase<bool>(
         Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[v4float:%\\w+]] = OpTypeVector [[float]] 4\n" +
-      "; CHECK: [[float_n1:%\\w+]] = OpConstant [[float]] -1\n" +
-      "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
-      "; CHECK: [[float_n3:%\\w+]] = OpConstant [[float]] -3\n" +
+      "; CHECK: [[v4float:%\\w+]] = OpTypeVector [[float]] 4{{[[:space:]]}}\n" +
+      "; CHECK: [[float_n1:%\\w+]] = OpConstant [[float]] -1{{[[:space:]]}}\n" +
+      "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1{{[[:space:]]}}\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
+      "; CHECK: [[float_n3:%\\w+]] = OpConstant [[float]] -3{{[[:space:]]}}\n" +
       "; CHECK: [[v4float_1_n2_n1_n3:%\\w+]] = OpConstantComposite [[v4float]] [[float_1]] [[float_n2]] [[float_n1]] [[float_n3]]\n" +
       "; CHECK: %2 = OpCopyObject [[v4float]] [[v4float_1_n2_n1_n3]]\n" +
         "%main = OpFunction %void None %void_func\n" +
@@ -4348,7 +4459,7 @@
         2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: scalar reicprocal
   // x / 0.5 = x * 2.0
@@ -4429,7 +4540,7 @@
     3, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeMulTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeMulTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: fold consecutive fmuls
   // (x * 3.0) * 2.0 = x * 6.0
@@ -4692,9 +4803,9 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
-      "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
-      "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2{{[[:space:]]}}\n" +
+      "; CHECK: OpConstant [[int]] -2147483648{{[[:space:]]}}\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" +
       "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" +
@@ -4712,9 +4823,9 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
-      "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
-      "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2{{[[:space:]]}}\n" +
+      "; CHECK: OpConstant [[int]] -2147483648{{[[:space:]]}}\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" +
       "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" +
@@ -4845,7 +4956,7 @@
     5, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeDivTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge consecutive fdiv
   // 4.0 / (2.0 / x) = 2.0 * x
@@ -5008,7 +5119,7 @@
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
       "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
       "; CHECK: %4 = OpSDiv [[int]] [[ld]] [[int_n2]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -5026,7 +5137,7 @@
     Header() +
       "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
       "; CHECK: OpConstant [[int]] -2147483648\n" +
-      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+      "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
       "; CHECK: %4 = OpSDiv [[int]] [[int_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -5091,7 +5202,7 @@
     5, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeAddTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeAddTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge add of negate
   // (-x) + 2 = 2 - x
@@ -5299,14 +5410,14 @@
     4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeSubTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeSubTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge sub of negate
   // (-x) - 2 = -2 - x
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
-      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+      "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
       "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -5340,7 +5451,7 @@
   InstructionFoldingCase<bool>(
     Header() +
       "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
-      "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" +
+      "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2{{[[:space:]]}}\n" +
       "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
       "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" +
       "%main = OpFunction %void None %void_func\n" +
@@ -5524,7 +5635,7 @@
     4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(SelectFoldingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(SelectFoldingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: Fold select with the same values for both sides
   InstructionFoldingCase<bool>(
@@ -5632,7 +5743,7 @@
       4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Extracting from result of consecutive shuffles of differing
     // size.
@@ -5775,7 +5886,7 @@
         4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Using OpDot to extract last element.
     InstructionFoldingCase<bool>(
@@ -5891,7 +6002,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(StoreMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+INSTANTIATE_TEST_SUITE_P(StoreMatchingTest, MatchingInstructionWithNoResultFoldingTest,
 ::testing::Values(
     // Test case 0: Remove store of undef.
     InstructionFoldingCase<bool>(
@@ -5920,7 +6031,7 @@
         0 /* OpStore */, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(VectorShuffleMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+INSTANTIATE_TEST_SUITE_P(VectorShuffleMatchingTest, MatchingInstructionWithNoResultFoldingTest,
 ::testing::Values(
     // Test case 0: Basic test 1
     InstructionFoldingCase<bool>(
@@ -6193,6 +6304,281 @@
         9, true)
 ));
 
+using EntryPointFoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(EntryPointFoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  Instruction* inst = nullptr;
+  inst = &*context->module()->entry_points().begin();
+  assert(inst && "Invalid test.  Could not find entry point instruction to fold.");
+  std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+  EXPECT_EQ(succeeded, tc.expected_result);
+  if (succeeded) {
+    Match(tc.test_body, context.get());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(OpEntryPointFoldingTest, EntryPointFoldingTest,
+::testing::Values(
+    // Test case 0: Basic test 1
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %3\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %3 %3 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true),
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %3 %4\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %3 %4 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                        "%4 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true),
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %4 %3\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %4 %4 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                        "%4 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true)
+));
+
+using SPV14FoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(SPV14FoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_4, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+  EXPECT_EQ(succeeded, tc.expected_result);
+  if (succeeded) {
+    Match(tc.test_body, context.get());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(SPV14FoldingTest, SPV14FoldingTest,
+::testing::Values(
+    // Test case 0: select vectors with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %1\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantTrue %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%int4 = OpTypeVector %int 4\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%1 = OpUndef %int4\n" +
+"%2 = OpUndef %int4\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %int4 %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true),
+    // Test case 1: select struct with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantFalse %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%struct = OpTypeStruct %int %int %int %int\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%1 = OpUndef %struct\n" +
+"%2 = OpUndef %struct\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %struct %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true),
+    // Test case 1: select array with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantFalse %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%int_4 = OpConstant %int 4\n" +
+"%array = OpTypeStruct %int %int %int %int\n" +
+"%1 = OpUndef %array\n" +
+"%2 = OpUndef %array\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %array %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true)
+));
+
+std::string FloatControlsHeader(const std::string& capabilities) {
+  std::string header = R"(
+OpCapability Shader
+)" + capabilities + R"(
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+)";
+
+  return header;
+}
+
+using FloatControlsFoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(FloatControlsFoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_4, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+  EXPECT_EQ(succeeded, tc.expected_result);
+  if (succeeded) {
+    Match(tc.test_body, context.get());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(FloatControlsFoldingTest, FloatControlsFoldingTest,
+::testing::Values(
+    // Test case 0: no folding with DenormPreserve
+    InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability DenormPreserve") +
+                                 "%1 = OpFAdd %float %float_0 %float_1\n" +
+                                 "OpReturn\n" +
+                                 "OpFunctionEnd\n"
+,
+                                 1, false),
+    // Test case 1: no folding with DenormFlushToZero
+    InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability DenormFlushToZero") +
+                                 "%1 = OpFAdd %float %float_0 %float_1\n" +
+                                 "OpReturn\n" +
+                                 "OpFunctionEnd\n"
+,
+                                 1, false),
+    // Test case 2: no folding with SignedZeroInfNanPreserve
+    InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability SignedZeroInfNanPreserve") +
+                                 "%1 = OpFAdd %float %float_0 %float_1\n" +
+                                 "OpReturn\n" +
+                                 "OpFunctionEnd\n"
+,
+                                 1, false),
+    // Test case 3: no folding with RoundingModeRTE
+    InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability RoundingModeRTE") +
+                                 "%1 = OpFAdd %float %float_0 %float_1\n" +
+                                 "OpReturn\n" +
+                                 "OpFunctionEnd\n"
+,
+                                 1, false),
+    // Test case 4: no folding with RoundingModeRTZ
+    InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability RoundingModeRTZ") +
+                                 "%1 = OpFAdd %float %float_0 %float_1\n" +
+                                 "OpReturn\n" +
+                                 "OpFunctionEnd\n"
+,
+                                 1, false)
+));
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/freeze_spec_const_test.cpp b/test/opt/freeze_spec_const_test.cpp
index 5cc7843..e5999ce 100644
--- a/test/opt/freeze_spec_const_test.cpp
+++ b/test/opt/freeze_spec_const_test.cpp
@@ -47,7 +47,7 @@
 }
 
 // Test each primary type.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
     ::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
         // Type declaration, original spec constant definition, expected frozen
diff --git a/test/opt/generate_webgpu_initializers_test.cpp b/test/opt/generate_webgpu_initializers_test.cpp
new file mode 100644
index 0000000..f35cf56
--- /dev/null
+++ b/test/opt/generate_webgpu_initializers_test.cpp
@@ -0,0 +1,347 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+typedef std::tuple<std::string, bool> GenerateWebGPUInitializersParam;
+
+using GlobalVariableTest =
+    PassTest<::testing::TestWithParam<GenerateWebGPUInitializersParam>>;
+using LocalVariableTest =
+    PassTest<::testing::TestWithParam<GenerateWebGPUInitializersParam>>;
+
+using GenerateWebGPUInitializersTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+void operator+=(std::vector<const char*>& lhs,
+                const std::vector<const char*>& rhs) {
+  lhs.reserve(lhs.size() + rhs.size());
+  for (auto* c : rhs) lhs.push_back(c);
+}
+
+std::string GetGlobalVariableTestString(std::string ptr_str,
+                                        std::string var_str,
+                                        std::string const_str = "") {
+  std::vector<const char*> result = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability VulkanMemoryModelKHR",
+               "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+               "OpMemoryModel Logical VulkanKHR",
+               "OpEntryPoint Vertex %1 \"shader\"",
+       "%uint = OpTypeInt 32 0",
+                ptr_str.c_str()};
+  // clang-format on
+
+  if (!const_str.empty()) result += const_str.c_str();
+
+  result += {
+      // clang-format off
+                var_str.c_str(),
+     "%uint_0 = OpConstant %uint 0",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+          "%1 = OpFunction %void None %7",
+          "%8 = OpLabel",
+               "OpStore %4 %uint_0",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetPointerString(std::string storage_type) {
+  std::string result = "%_ptr_";
+  result += storage_type + "_uint = OpTypePointer ";
+  result += storage_type + " %uint";
+  return result;
+}
+
+std::string GetGlobalVariableString(std::string storage_type,
+                                    bool initialized) {
+  std::string result = "%4 = OpVariable %_ptr_";
+  result += storage_type + "_uint ";
+  result += storage_type;
+  if (initialized) result += " %9";
+  return result;
+}
+
+std::string GetUninitializedGlobalVariableTestString(std::string storage_type) {
+  return GetGlobalVariableTestString(
+      GetPointerString(storage_type),
+      GetGlobalVariableString(storage_type, false));
+}
+
+std::string GetNullConstantString() { return "%9 = OpConstantNull %uint"; }
+
+std::string GetInitializedGlobalVariableTestString(std::string storage_type) {
+  return GetGlobalVariableTestString(
+      GetPointerString(storage_type),
+      GetGlobalVariableString(storage_type, true), GetNullConstantString());
+}
+
+TEST_P(GlobalVariableTest, Check) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool changed = std::get<1>(GetParam());
+  std::string input = GetUninitializedGlobalVariableTestString(storage_class);
+  std::string expected =
+      changed ? GetInitializedGlobalVariableTestString(storage_class) : input;
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input, expected,
+                                                        /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    GenerateWebGPUInitializers, GlobalVariableTest,
+    ::testing::ValuesIn(std::vector<GenerateWebGPUInitializersParam>({
+       std::make_tuple("Private", true),
+       std::make_tuple("Output", true),
+       std::make_tuple("Function", true),
+       std::make_tuple("UniformConstant", false),
+       std::make_tuple("Input", false),
+       std::make_tuple("Uniform", false),
+       std::make_tuple("Workgroup", false)
+    })));
+// clang-format on
+
+std::string GetLocalVariableTestString(std::string ptr_str, std::string var_str,
+                                       std::string const_str = "") {
+  std::vector<const char*> result = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability VulkanMemoryModelKHR",
+               "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+               "OpMemoryModel Logical VulkanKHR",
+               "OpEntryPoint Vertex %1 \"shader\"",
+       "%uint = OpTypeInt 32 0",
+                ptr_str.c_str(),
+     "%uint_0 = OpConstant %uint 0",
+       "%void = OpTypeVoid",
+          "%6 = OpTypeFunction %void"};
+  // clang-format on
+
+  if (!const_str.empty()) result += const_str.c_str();
+
+  result += {
+      // clang-format off
+          "%1 = OpFunction %void None %6",
+          "%7 = OpLabel",
+                var_str.c_str(),
+               "OpStore %8 %uint_0"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetLocalVariableString(std::string storage_type, bool initialized) {
+  std::string result = "%8 = OpVariable %_ptr_";
+  result += storage_type + "_uint ";
+  result += storage_type;
+  if (initialized) result += " %9";
+  return result;
+}
+
+std::string GetUninitializedLocalVariableTestString(std::string storage_type) {
+  return GetLocalVariableTestString(
+      GetPointerString(storage_type),
+      GetLocalVariableString(storage_type, false));
+}
+
+std::string GetInitializedLocalVariableTestString(std::string storage_type) {
+  return GetLocalVariableTestString(GetPointerString(storage_type),
+                                    GetLocalVariableString(storage_type, true),
+                                    GetNullConstantString());
+}
+
+TEST_P(LocalVariableTest, Check) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool changed = std::get<1>(GetParam());
+
+  std::string input = GetUninitializedLocalVariableTestString(storage_class);
+  std::string expected =
+      changed ? GetInitializedLocalVariableTestString(storage_class) : input;
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input, expected,
+                                                        /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    GenerateWebGPUInitializers, LocalVariableTest,
+    ::testing::ValuesIn(std::vector<GenerateWebGPUInitializersParam>({
+       std::make_tuple("Private", true),
+       std::make_tuple("Output", true),
+       std::make_tuple("Function", true),
+       std::make_tuple("UniformConstant", false),
+       std::make_tuple("Input", false),
+       std::make_tuple("Uniform", false),
+       std::make_tuple("Workgroup", false)
+    })));
+// clang-format on
+
+TEST_F(GenerateWebGPUInitializersTest, AlreadyInitializedUnchanged) {
+  std::vector<const char*> spirv = {
+      // clang-format off
+                       "OpCapability Shader",
+                       "OpCapability VulkanMemoryModelKHR",
+                       "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                       "OpMemoryModel Logical VulkanKHR",
+                       "OpEntryPoint Vertex %1 \"shader\"",
+               "%uint = OpTypeInt 32 0",
+  "%_ptr_Private_uint = OpTypePointer Private %uint",
+             "%uint_0 = OpConstant %uint 0",
+                  "%5 = OpVariable %_ptr_Private_uint Private %uint_0",
+               "%void = OpTypeVoid",
+                  "%7 = OpTypeFunction %void",
+                  "%1 = OpFunction %void None %7",
+                  "%8 = OpLabel",
+                       "OpReturn",
+                       "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string str = JoinAllInsts(spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(str, str,
+                                                        /* skip_nop = */ false);
+}
+
+TEST_F(GenerateWebGPUInitializersTest, AmbigiousArrays) {
+  std::vector<const char*> input_spirv = {
+      // clang-format off
+                                   "OpCapability Shader",
+                                   "OpCapability VulkanMemoryModelKHR",
+                                   "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                                   "OpMemoryModel Logical VulkanKHR",
+                                   "OpEntryPoint Vertex %1 \"shader\"",
+                           "%uint = OpTypeInt 32 0",
+                         "%uint_2 = OpConstant %uint 2",
+               "%_arr_uint_uint_2 = OpTypeArray %uint %uint_2",
+             "%_arr_uint_uint_2_0 = OpTypeArray %uint %uint_2",
+  "%_ptr_Private__arr_uint_uint_2 = OpTypePointer Private %_arr_uint_uint_2",
+"%_ptr_Private__arr_uint_uint_2_0 = OpTypePointer Private %_arr_uint_uint_2_0",
+                              "%8 = OpConstantNull %_arr_uint_uint_2_0",
+                              "%9 = OpVariable %_ptr_Private__arr_uint_uint_2 Private",
+                             "%10 = OpVariable %_ptr_Private__arr_uint_uint_2_0 Private %8",
+                           "%void = OpTypeVoid",
+                             "%12 = OpTypeFunction %void",
+                              "%1 = OpFunction %void None %12",
+                             "%13 = OpLabel",
+                                   "OpReturn",
+                                   "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string input_str = JoinAllInsts(input_spirv);
+
+  std::vector<const char*> expected_spirv = {
+      // clang-format off
+                                   "OpCapability Shader",
+                                   "OpCapability VulkanMemoryModelKHR",
+                                   "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                                   "OpMemoryModel Logical VulkanKHR",
+                                   "OpEntryPoint Vertex %1 \"shader\"",
+                           "%uint = OpTypeInt 32 0",
+                         "%uint_2 = OpConstant %uint 2",
+               "%_arr_uint_uint_2 = OpTypeArray %uint %uint_2",
+             "%_arr_uint_uint_2_0 = OpTypeArray %uint %uint_2",
+  "%_ptr_Private__arr_uint_uint_2 = OpTypePointer Private %_arr_uint_uint_2",
+"%_ptr_Private__arr_uint_uint_2_0 = OpTypePointer Private %_arr_uint_uint_2_0",
+                              "%8 = OpConstantNull %_arr_uint_uint_2_0",
+                             "%14 = OpConstantNull %_arr_uint_uint_2",
+                              "%9 = OpVariable %_ptr_Private__arr_uint_uint_2 Private %14",
+                             "%10 = OpVariable %_ptr_Private__arr_uint_uint_2_0 Private %8",
+                           "%void = OpTypeVoid",
+                             "%12 = OpTypeFunction %void",
+                              "%1 = OpFunction %void None %12",
+                             "%13 = OpLabel",
+                                   "OpReturn",
+                                   "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string expected_str = JoinAllInsts(expected_spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input_str, expected_str,
+                                                        /* skip_nop = */ false);
+}
+
+TEST_F(GenerateWebGPUInitializersTest, AmbigiousStructs) {
+  std::vector<const char*> input_spirv = {
+      // clang-format off
+                          "OpCapability Shader",
+                          "OpCapability VulkanMemoryModelKHR",
+                          "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                          "OpMemoryModel Logical VulkanKHR",
+                          "OpEntryPoint Vertex %1 \"shader\"",
+                  "%uint = OpTypeInt 32 0",
+             "%_struct_3 = OpTypeStruct %uint",
+             "%_struct_4 = OpTypeStruct %uint",
+"%_ptr_Private__struct_3 = OpTypePointer Private %_struct_3",
+"%_ptr_Private__struct_4 = OpTypePointer Private %_struct_4",
+                     "%7 = OpConstantNull %_struct_3",
+                     "%8 = OpVariable %_ptr_Private__struct_3 Private %7",
+                     "%9 = OpVariable %_ptr_Private__struct_4 Private",
+                  "%void = OpTypeVoid",
+                    "%11 = OpTypeFunction %void",
+                     "%1 = OpFunction %void None %11",
+                    "%12 = OpLabel",
+                          "OpReturn",
+                          "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string input_str = JoinAllInsts(input_spirv);
+
+  std::vector<const char*> expected_spirv = {
+      // clang-format off
+                          "OpCapability Shader",
+                          "OpCapability VulkanMemoryModelKHR",
+                          "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                          "OpMemoryModel Logical VulkanKHR",
+                          "OpEntryPoint Vertex %1 \"shader\"",
+                  "%uint = OpTypeInt 32 0",
+             "%_struct_3 = OpTypeStruct %uint",
+             "%_struct_4 = OpTypeStruct %uint",
+"%_ptr_Private__struct_3 = OpTypePointer Private %_struct_3",
+"%_ptr_Private__struct_4 = OpTypePointer Private %_struct_4",
+                     "%7 = OpConstantNull %_struct_3",
+                     "%8 = OpVariable %_ptr_Private__struct_3 Private %7",
+                    "%13 = OpConstantNull %_struct_4",
+                     "%9 = OpVariable %_ptr_Private__struct_4 Private %13",
+                  "%void = OpTypeVoid",
+                    "%11 = OpTypeFunction %void",
+                     "%1 = OpFunction %void None %11",
+                    "%12 = OpLabel",
+                          "OpReturn",
+                          "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string expected_str = JoinAllInsts(expected_spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input_str, expected_str,
+                                                        /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 44a9698..7170812 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -2421,7 +2421,8 @@
 OpStore %38 %float_1
 OpBranch %40
 %43 = OpLabel
-OpUnreachable
+OpStore %38 %float_1
+OpBranch %40
 %41 = OpLabel
 OpBranchConditional %false %39 %40
 %40 = OpLabel
@@ -2446,7 +2447,7 @@
 %36 = OpLabel
 OpReturnValue %float_1
 %35 = OpLabel
-OpUnreachable
+OpReturnValue %float_1
 OpFunctionEnd
 )";
 
@@ -3058,6 +3059,7 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%15 = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -3067,10 +3069,11 @@
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 )";
 
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
@@ -3086,6 +3089,7 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%15 = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -3095,15 +3099,16 @@
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
 %14 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 )";
 
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index a426ce0..6fe27c6 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -13,6 +13,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Bindless Check Instrumentation Tests.
+// Tests ending with V2 use version 2 record format.
+
 #include <string>
 #include <vector>
 
@@ -252,7 +255,7 @@
           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);
+      true, true, 7u, 23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, NoInstrumentConstIndexInbounds) {
@@ -332,7 +335,8 @@
 )";
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(before, before, true, true);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(before, before, true, true, 7u,
+                                               23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, InstrumentMultipleInstructions) {
@@ -627,275 +631,7 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InstBindlessCheckPass>(
       defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
-}
-
-TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
-  // This test verifies that the pass resuses existing constants, types
-  // and builtin variables.  This test was created by editing the SPIR-V
-  // from the Simple test.
-
-  const std::string defs_before =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %85 DescriptorSet 7
-OpDecorate %85 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_20_uint_128 = OpTypeArray %20 %uint_128
-%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
-%35 = OpTypeSampler
-%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
-%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
-%39 = OpTypeSampledImage %20
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
-%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%131 = OpConstantNull %v4float
-)";
-
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %10 DescriptorSet 7
-OpDecorate %10 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_34 Block
-OpMemberDecorate %_struct_34 0 Offset 0
-OpMemberDecorate %_struct_34 1 Offset 4
-OpDecorate %74 DescriptorSet 7
-OpDecorate %74 Binding 0
-%void = OpTypeVoid
-%12 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_18_uint_128 = OpTypeArray %18 %uint_128
-%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
-%26 = OpTypeSampler
-%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
-%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
-%28 = OpTypeSampledImage %18
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
-%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%50 = OpConstantNull %v4float
-%68 = OpTypeFunction %void %uint %uint %uint %uint
-%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%uint_82 = OpConstant %uint 82
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
-%5 = OpLabel
-%53 = OpLoad %v2float %i_vTextureCoords
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%64 = OpLoad %uint %63
-%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
-%67 = OpLoad %35 %g_sAniso
-%78 = OpLoad %20 %65
-%79 = OpSampledImage %39 %78 %67
-%71 = OpImageSampleImplicitLod %v4float %79 %53
-OpStore %_entryPointOutput_vColor %71
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %12
-%51 = OpLabel
-%52 = OpLoad %v2float %i_vTextureCoords
-%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%54 = OpLoad %uint %53
-%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
-%56 = OpLoad %26 %g_sAniso
-%57 = OpLoad %18 %55
-%58 = OpSampledImage %28 %57 %56
-%60 = OpULessThan %bool %54 %uint_128
-OpSelectionMerge %61 None
-OpBranchConditional %60 %62 %63
-%62 = OpLabel
-%64 = OpLoad %18 %55
-%65 = OpSampledImage %28 %64 %56
-%66 = OpImageSampleImplicitLod %v4float %65 %52
-OpBranch %61
-%63 = OpLabel
-%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
-OpBranch %61
-%61 = OpLabel
-%106 = OpPhi %v4float %66 %62 %50 %63
-OpStore %_entryPointOutput_vColor %106
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%67 = OpFunction %void None %68
-%69 = OpFunctionParameter %uint
-%70 = OpFunctionParameter %uint
-%71 = OpFunctionParameter %uint
-%72 = OpFunctionParameter %uint
-%73 = OpLabel
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
-%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
-%77 = OpIAdd %uint %76 %uint_9
-%78 = OpArrayLength %uint %74 1
-%79 = OpULessThanEqual %bool %77 %78
-OpSelectionMerge %80 None
-OpBranchConditional %79 %81 %80
-%81 = OpLabel
-%82 = OpIAdd %uint %76 %uint_0
-%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
-OpStore %83 %uint_9
-%84 = OpIAdd %uint %76 %uint_1
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
-OpStore %85 %uint_23
-%86 = OpIAdd %uint %76 %uint_2
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
-OpStore %87 %69
-%88 = OpIAdd %uint %76 %uint_3
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
-OpStore %89 %uint_4
-%90 = OpLoad %v4float %gl_FragCoord
-%91 = OpBitcast %v4uint %90
-%92 = OpCompositeExtract %uint %91 0
-%93 = OpIAdd %uint %76 %uint_4
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
-OpStore %94 %92
-%95 = OpCompositeExtract %uint %91 1
-%96 = OpIAdd %uint %76 %uint_5
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
-OpStore %97 %95
-%98 = OpIAdd %uint %76 %uint_6
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
-OpStore %99 %70
-%100 = OpIAdd %uint %76 %uint_7
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
-OpStore %101 %71
-%102 = OpIAdd %uint %76 %uint_8
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
-OpStore %103 %72
-OpBranch %80
-%80 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
+      true, 7u, 23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, InstrumentOpImage) {
@@ -1123,7 +859,7 @@
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InstBindlessCheckPass>(
       defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
+      true, 7u, 23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, InstrumentSampledImage) {
@@ -1346,7 +1082,7 @@
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InstBindlessCheckPass>(
       defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
+      true, 7u, 23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, InstrumentImageWrite) {
@@ -1571,7 +1307,7 @@
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InstBindlessCheckPass>(
       defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
+      true, 7u, 23u, false, false, 1u);
 }
 
 TEST_F(InstBindlessTest, InstrumentVertexSimple) {
@@ -1845,12 +1581,8977 @@
   // 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, 1u);
+}
+
+TEST_F(InstBindlessTest, MultipleDebugFunctions) {
+  // Same source as Simple, but compiled -g and not optimized, especially not
+  // inlined. The OpSource has had the source extracted for the sake of brevity.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%2 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%1 = OpString "foo5.frag"
+OpSource HLSL 500 %1
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%13 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_21_uint_128 = OpTypeArray %21 %uint_128
+%_ptr_UniformConstant__arr_21_uint_128 = OpTypePointer UniformConstant %_arr_21_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_21_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%36 = OpTypeSampler
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%g_sAniso = OpVariable %_ptr_UniformConstant_36 UniformConstant
+%40 = OpTypeSampledImage %21
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo5.frag"
+OpSource HLSL 500 %5
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_77 Block
+OpMemberDecorate %_struct_77 0 Offset 0
+OpMemberDecorate %_struct_77 1 Offset 4
+OpDecorate %79 DescriptorSet 7
+OpDecorate %79 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%18 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%23 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%27 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_27_uint_128 = OpTypeArray %27 %uint_128
+%_ptr_UniformConstant__arr_27_uint_128 = OpTypePointer UniformConstant %_arr_27_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_27_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%37 = OpTypeSampledImage %27
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%70 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_77 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_77 = OpTypePointer StorageBuffer %_struct_77
+%79 = OpVariable %_ptr_StorageBuffer__struct_77 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_93 = OpConstant %uint 93
+%125 = OpConstantNull %v4float
+)";
+
+  const std::string func1_before =
+      R"(%MainPs = OpFunction %void None %4
+%6 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %1 21 0
+%54 = OpLoad %v2float %i_vTextureCoords
+%55 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %55 %54
+%59 = OpLoad %PS_INPUT %i_0
+OpStore %param %59
+%60 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%61 = OpCompositeExtract %v4float %60 0
+OpStore %_entryPointOutput_vColor %61
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func1_after =
+      R"(%MainPs = OpFunction %void None %18
+%42 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 21 0
+%43 = OpLoad %v2float %i_vTextureCoords
+%44 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %44 %43
+%45 = OpLoad %PS_INPUT %i_0
+OpStore %param %45
+%46 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%47 = OpCompositeExtract %v4float %46 0
+OpStore %_entryPointOutput_vColor %47
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func2_before =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %13
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%16 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %1 24 0
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%34 = OpAccessChain %_ptr_UniformConstant_21 %g_tColor %32
+%35 = OpLoad %21 %34
+%39 = OpLoad %36 %g_sAniso
+%41 = OpSampledImage %40 %35 %39
+%43 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %41 %44
+%47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %47 %45
+OpLine %1 25 0
+%48 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %48
+OpFunctionEnd
+)";
+
+  const std::string func2_after =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %23
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%48 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 24 0
+%49 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%50 = OpLoad %uint %49
+%51 = OpAccessChain %_ptr_UniformConstant_27 %g_tColor %50
+%52 = OpLoad %27 %51
+%53 = OpLoad %35 %g_sAniso
+%54 = OpSampledImage %37 %52 %53
+%55 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%56 = OpLoad %v2float %55
+%62 = OpULessThan %bool %50 %uint_128
+OpSelectionMerge %63 None
+OpBranchConditional %62 %64 %65
+%64 = OpLabel
+%66 = OpLoad %27 %51
+%67 = OpSampledImage %37 %66 %53
+%68 = OpImageSampleImplicitLod %v4float %67 %56
+OpBranch %63
+%65 = OpLabel
+%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+OpBranch %63
+%63 = OpLabel
+%126 = OpPhi %v4float %68 %64 %125 %65
+%58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %58 %126
+OpLine %5 25 0
+%59 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %59
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%69 = OpFunction %void None %70
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpFunctionParameter %uint
+%74 = OpFunctionParameter %uint
+%75 = OpLabel
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_0
+%84 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_9
+%85 = OpIAdd %uint %84 %uint_9
+%86 = OpArrayLength %uint %79 1
+%87 = OpULessThanEqual %bool %85 %86
+OpSelectionMerge %88 None
+OpBranchConditional %87 %89 %88
+%89 = OpLabel
+%90 = OpIAdd %uint %84 %uint_0
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %90
+OpStore %92 %uint_9
+%94 = OpIAdd %uint %84 %uint_1
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %94
+OpStore %95 %uint_23
+%97 = OpIAdd %uint %84 %uint_2
+%98 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %97
+OpStore %98 %71
+%100 = OpIAdd %uint %84 %uint_3
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %100
+OpStore %101 %uint_4
+%104 = OpLoad %v4float %gl_FragCoord
+%106 = OpBitcast %v4uint %104
+%107 = OpCompositeExtract %uint %106 0
+%108 = OpIAdd %uint %84 %uint_4
+%109 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %108
+OpStore %109 %107
+%110 = OpCompositeExtract %uint %106 1
+%112 = OpIAdd %uint %84 %uint_5
+%113 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %112
+OpStore %113 %110
+%115 = OpIAdd %uint %84 %uint_6
+%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115
+OpStore %116 %72
+%118 = OpIAdd %uint %84 %uint_7
+%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118
+OpStore %119 %73
+%121 = OpIAdd %uint %84 %uint_8
+%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121
+OpStore %122 %74
+OpBranch %88
+%88 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func1_before + func2_before,
+      defs_after + func1_after + func2_after + output_func, true, true, 7u, 23u,
+      false, false, 1u);
+}
+
+TEST_F(InstBindlessTest, RuntimeArray) {
+  // This test verifies that the pass will correctly instrument shader
+  // with runtime descriptor array. This test was created by editing the
+  // SPIR-V from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_rarr_20 = OpTypeRuntimeArray %20
+%_ptr_UniformConstant__arr_20 = OpTypePointer UniformConstant %_rarr_20
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_46 Block
+OpMemberDecorate %_struct_46 0 Offset 0
+OpDecorate %48 DescriptorSet 7
+OpDecorate %48 Binding 1
+OpDecorate %_struct_71 Block
+OpMemberDecorate %_struct_71 0 Offset 0
+OpMemberDecorate %_struct_71 1 Offset 4
+OpDecorate %73 DescriptorSet 7
+OpDecorate %73 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_runtimearr_16 = OpTypeRuntimeArray %16
+%_ptr_UniformConstant__runtimearr_16 = OpTypePointer UniformConstant %_runtimearr_16
+%g_tColor = OpVariable %_ptr_UniformConstant__runtimearr_16 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%uint_2 = OpConstant %uint 2
+%41 = OpTypeFunction %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_46 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46
+%48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%65 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_71 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_71 = OpTypePointer StorageBuffer %_struct_71
+%73 = OpVariable %_ptr_StorageBuffer__struct_71 StorageBuffer
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_59 = OpConstant %uint 59
+%116 = OpConstantNull %v4float
+%119 = OpTypeFunction %uint %uint %uint %uint %uint
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+%67 = OpLoad %35 %g_sAniso
+%68 = OpSampledImage %39 %66 %67
+%71 = OpImageSampleImplicitLod %v4float %68 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+%55 = OpFunctionCall %uint %40 %uint_2 %uint_2
+%57 = OpULessThan %bool %32 %55
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+%59 = OpLabel
+%61 = OpLoad %16 %33
+%62 = OpSampledImage %26 %61 %35
+%136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32
+%137 = OpINotEqual %bool %136 %uint_0
+OpSelectionMerge %138 None
+OpBranchConditional %137 %139 %140
+%139 = OpLabel
+%141 = OpLoad %16 %33
+%142 = OpSampledImage %26 %141 %35
+%143 = OpImageSampleImplicitLod %v4float %142 %30
+OpBranch %138
+%140 = OpLabel
+%144 = OpFunctionCall %void %64 %uint_59 %uint_1 %32 %uint_0
+OpBranch %138
+%138 = OpLabel
+%145 = OpPhi %v4float %143 %139 %116 %140
+OpBranch %58
+%60 = OpLabel
+%115 = OpFunctionCall %void %64 %uint_59 %uint_0 %32 %55
+OpBranch %58
+%58 = OpLabel
+%117 = OpPhi %v4float %145 %138 %116 %60
+OpStore %_entryPointOutput_vColor %117
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string new_funcs =
+      R"(%40 = OpFunction %uint None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpLabel
+%50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %42
+%51 = OpLoad %uint %50
+%52 = OpIAdd %uint %51 %43
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %52
+%54 = OpLoad %uint %53
+OpReturnValue %54
+OpFunctionEnd
+%64 = OpFunction %void None %65
+%66 = OpFunctionParameter %uint
+%67 = OpFunctionParameter %uint
+%68 = OpFunctionParameter %uint
+%69 = OpFunctionParameter %uint
+%70 = OpLabel
+%74 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_0
+%77 = OpAtomicIAdd %uint %74 %uint_4 %uint_0 %uint_9
+%78 = OpIAdd %uint %77 %uint_9
+%79 = OpArrayLength %uint %73 1
+%80 = OpULessThanEqual %bool %78 %79
+OpSelectionMerge %81 None
+OpBranchConditional %80 %82 %81
+%82 = OpLabel
+%83 = OpIAdd %uint %77 %uint_0
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %83
+OpStore %84 %uint_9
+%86 = OpIAdd %uint %77 %uint_1
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %86
+OpStore %87 %uint_23
+%88 = OpIAdd %uint %77 %uint_2
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %88
+OpStore %89 %66
+%91 = OpIAdd %uint %77 %uint_3
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %91
+OpStore %92 %uint_4
+%95 = OpLoad %v4float %gl_FragCoord
+%97 = OpBitcast %v4uint %95
+%98 = OpCompositeExtract %uint %97 0
+%99 = OpIAdd %uint %77 %uint_4
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %99
+OpStore %100 %98
+%101 = OpCompositeExtract %uint %97 1
+%103 = OpIAdd %uint %77 %uint_5
+%104 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %103
+OpStore %104 %101
+%106 = OpIAdd %uint %77 %uint_6
+%107 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %106
+OpStore %107 %67
+%109 = OpIAdd %uint %77 %uint_7
+%110 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %109
+OpStore %110 %68
+%112 = OpIAdd %uint %77 %uint_8
+%113 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %112
+OpStore %113 %69
+OpBranch %81
+%81 = OpLabel
+OpReturn
+OpFunctionEnd
+%118 = OpFunction %uint None %119
+%120 = OpFunctionParameter %uint
+%121 = OpFunctionParameter %uint
+%122 = OpFunctionParameter %uint
+%123 = OpFunctionParameter %uint
+%124 = OpLabel
+%125 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %120
+%126 = OpLoad %uint %125
+%127 = OpIAdd %uint %126 %121
+%128 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %127
+%129 = OpLoad %uint %128
+%130 = OpIAdd %uint %129 %122
+%131 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %130
+%132 = OpLoad %uint %131
+%133 = OpIAdd %uint %132 %123
+%134 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %133
+%135 = OpLoad %uint %134
+OpReturnValue %135
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
       true);
 }
 
+TEST_F(InstBindlessTest, NoInstrumentNonBindless) {
+  // This test verifies that the pass will correctly not instrument vanilla
+  // texture sample.
+  //
+  // Texture2D g_tColor;
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   ps_output.vColor =
+  //       g_tColor.Sample(g_sAniso, i.vTextureCoords.xy);
+  //   return ps_output;
+  // }
+
+  const std::string whole_file =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%24 = OpImageSampleImplicitLod %v4float %23 %20
+OpStore %_entryPointOutput_vColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(whole_file, whole_file, true,
+                                               true, 7u, 23u, false, false, 1u);
+}
+
+TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptor) {
+  // This test verifies that the pass will correctly instrument vanilla
+  // texture sample on a scalar descriptor with an initialization check if the
+  // input_init_enable argument is set to true. This can happen when the
+  // descriptor indexing extension is enabled in the API but the SPIR-V
+  // 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
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_35 Block
+OpMemberDecorate %_struct_35 0 Offset 0
+OpDecorate %37 DescriptorSet 7
+OpDecorate %37 Binding 1
+OpDecorate %_struct_67 Block
+OpMemberDecorate %_struct_67 0 Offset 0
+OpMemberDecorate %_struct_67 1 Offset 4
+OpDecorate %69 DescriptorSet 7
+OpDecorate %69 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%28 = OpTypeFunction %uint %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_35 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_35 = OpTypePointer StorageBuffer %_struct_35
+%37 = OpVariable %_ptr_StorageBuffer__struct_35 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%uint_1 = OpConstant %uint 1
+%61 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_67 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67
+%69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_39 = OpConstant %uint 39
+%113 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%24 = OpImageSampleImplicitLod %v4float %23 %20
+OpStore %_entryPointOutput_vColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0
+%52 = OpINotEqual %bool %50 %uint_0
+OpSelectionMerge %54 None
+OpBranchConditional %52 %55 %56
+%55 = OpLabel
+%57 = OpLoad %12 %g_tColor
+%58 = OpSampledImage %16 %57 %22
+%59 = OpImageSampleImplicitLod %v4float %58 %20
+OpBranch %54
+%56 = OpLabel
+%112 = OpFunctionCall %void %60 %uint_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_9
+%74 = OpIAdd %uint %73 %uint_9
+%75 = OpArrayLength %uint %69 1
+%76 = OpULessThanEqual %bool %74 %75
+OpSelectionMerge %77 None
+OpBranchConditional %76 %78 %77
+%78 = OpLabel
+%79 = OpIAdd %uint %73 %uint_0
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %79
+OpStore %80 %uint_9
+%82 = OpIAdd %uint %73 %uint_1
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %82
+OpStore %83 %uint_23
+%85 = OpIAdd %uint %73 %uint_2
+%86 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %85
+OpStore %86 %62
+%88 = OpIAdd %uint %73 %uint_3
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %88
+OpStore %89 %uint_4
+%92 = OpLoad %v4float %gl_FragCoord
+%94 = OpBitcast %v4uint %92
+%95 = OpCompositeExtract %uint %94 0
+%96 = OpIAdd %uint %73 %uint_4
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %96
+OpStore %97 %95
+%98 = OpCompositeExtract %uint %94 1
+%100 = OpIAdd %uint %73 %uint_5
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %100
+OpStore %101 %98
+%103 = OpIAdd %uint %73 %uint_6
+%104 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %103
+OpStore %104 %63
+%106 = OpIAdd %uint %73 %uint_7
+%107 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %106
+OpStore %107 %64
+%109 = OpIAdd %uint %73 %uint_8
+%110 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %109
+OpStore %110 %65
+OpBranch %77
+%77 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, SPV14AddToEntryPoint) {
+  const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "foo" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpDecorate [[v1]] DescriptorSet 7
+; CHECK: OpDecorate [[v2]] DescriptorSet 7
+; CHECK: [[v1]] = OpVariable {{%\w+}} StorageBuffer
+; CHECK: [[v2]] = OpVariable {{%\w+}} StorageBuffer
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %foo "foo" %gid %image_var %sampler_var
+OpExecutionMode %foo OriginUpperLeft
+OpDecorate %image_var DescriptorSet 0
+OpDecorate %image_var Binding 0
+OpDecorate %sampler_var DescriptorSet 0
+OpDecorate %sampler_var Binding 1
+OpDecorate %gid DescriptorSet 0
+OpDecorate %gid Binding 2
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%v3int = OpTypeVector %int 3
+%float = OpTypeFloat 32
+%v3float = OpTypeVector %float 3
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %v3int
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ptr_ssbo_v3int = OpTypePointer StorageBuffer %v3int
+%gid = OpVariable %ptr_ssbo_struct StorageBuffer
+%image = OpTypeImage %float 3D 0 0 0 1 Unknown
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%sampled = OpTypeSampledImage %image
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+%gep = OpAccessChain %ptr_ssbo_v3int %gid %int_0
+%ld_gid = OpLoad %v3int %gep
+%convert = OpConvertUToF %v3float %ld_gid
+%sampled_image = OpSampledImage %sampled %ld_image %ld_sampler
+%sample = OpImageSampleImplicitLod %v4float %sampled_image %convert
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true);
+}
+
+TEST_F(InstBindlessTest, SPV14AddToEntryPoints) {
+  const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "foo" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpEntryPoint Fragment {{%\w+}} "bar" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpDecorate [[v1]] DescriptorSet 7
+; CHECK: OpDecorate [[v2]] DescriptorSet 7
+; CHECK: [[v1]] = OpVariable {{%\w+}} StorageBuffer
+; CHECK: [[v2]] = OpVariable {{%\w+}} StorageBuffer
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %foo "foo" %gid %image_var %sampler_var
+OpEntryPoint Fragment %foo "bar" %gid %image_var %sampler_var
+OpExecutionMode %foo OriginUpperLeft
+OpDecorate %image_var DescriptorSet 0
+OpDecorate %image_var Binding 0
+OpDecorate %sampler_var DescriptorSet 0
+OpDecorate %sampler_var Binding 1
+OpDecorate %gid DescriptorSet 0
+OpDecorate %gid Binding 2
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%v3int = OpTypeVector %int 3
+%float = OpTypeFloat 32
+%v3float = OpTypeVector %float 3
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %v3int
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ptr_ssbo_v3int = OpTypePointer StorageBuffer %v3int
+%gid = OpVariable %ptr_ssbo_struct StorageBuffer
+%image = OpTypeImage %float 3D 0 0 0 1 Unknown
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%sampled = OpTypeSampledImage %image
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+%gep = OpAccessChain %ptr_ssbo_v3int %gid %int_0
+%ld_gid = OpLoad %v3int %gep
+%convert = OpConvertUToF %v3float %ld_gid
+%sampled_image = OpSampledImage %sampled %ld_image %ld_sampler
+%sample = OpImageSampleImplicitLod %v4float %sampled_image %convert
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedUBOArray) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer[];
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer[nu_ii].a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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_9 = OpConstant %uint 9
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 = OpINotEqual %bool %122 %uint_0
+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_9
+%62 = OpIAdd %uint %61 %uint_9
+%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_9
+%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_6
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
+OpStore %92 %51
+%94 = OpIAdd %uint %61 %uint_7
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
+OpStore %95 %52
+%97 = OpIAdd %uint %61 %uint_8
+%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
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArrayDeprecated) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  buffer bname { float b; }  storageBuffer[];
+  //
+  // void main()
+  // {
+  //     b = storageBuffer[nu_ii].b;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 BufferBlock
+OpDecorate %storageBuffer DescriptorSet 0
+OpDecorate %storageBuffer Binding 3
+OpDecorate %nu_ii Flat
+OpDecorate %nu_ii Location 0
+OpDecorate %nu_ii NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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_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_Uniform_float = OpTypePointer Uniform %float
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+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 BufferBlock
+OpDecorate %storageBuffer DescriptorSet 0
+OpDecorate %storageBuffer Binding 3
+OpDecorate %nu_ii Flat
+OpDecorate %nu_ii Location 0
+OpDecorate %nu_ii NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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_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_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_9 = OpConstant %uint 9
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_Uniform_float %storageBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 %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 = OpINotEqual %bool %122 %uint_0
+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_9
+%62 = OpIAdd %uint %61 %uint_9
+%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_9
+%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_6
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
+OpStore %92 %51
+%94 = OpIAdd %uint %61 %uint_7
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
+OpStore %95 %52
+%97 = OpIAdd %uint %61 %uint_8
+%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
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArray) {
+  // Same as Deprecated but declaring as StorageBuffer Block
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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_9 = OpConstant %uint 9
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 = OpINotEqual %bool %122 %uint_0
+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_9
+%62 = OpIAdd %uint %61 %uint_9
+%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_9
+%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_6
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
+OpStore %92 %51
+%94 = OpIAdd %uint %61 %uint_7
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
+OpStore %95 %52
+%97 = OpIAdd %uint %61 %uint_8
+%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
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstInitLoadUBOScalar) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) out float b;
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer;
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer.a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b
+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
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  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_9 = OpConstant %uint 9
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_32 = OpConstant %uint 32
+%104 = OpConstantNull %float
+)";
+
+  const std::string func_before =
+      R"(%main = OpFunction %void None %3
+%5 = OpLabel
+%15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0
+%16 = OpLoad %float %15
+OpStore %b %16
+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 = OpINotEqual %bool %43 %uint_0
+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_9
+%65 = OpIAdd %uint %64 %uint_9
+%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_9
+%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_6
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %94
+OpStore %95 %54
+%97 = OpIAdd %uint %64 %uint_7
+%98 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %97
+OpStore %98 %55
+%100 = OpIAdd %uint %64 %uint_8
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %100
+OpStore %101 %56
+OpBranch %68
+%68 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstBoundsInitStoreUnsizedSSBOArray) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=1) in float b;
+  //
+  // layout(binding=4)  buffer bname { float b; }  storageBuffer[];
+  //
+  // void main()
+  // {
+  //     storageBuffer[nu_ii].b = b;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %nu_ii %b
+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 NonUniformEXT
+OpDecorate %14 NonUniformEXT
+OpDecorate %b Location 1
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+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_9 = OpConstant %uint 9
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_45 = OpConstant %uint 45
+%102 = OpTypeFunction %uint %uint %uint %uint %uint
+)";
+
+  const std::string func_before =
+      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
+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 = OpINotEqual %bool %119 %uint_0
+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_9
+%60 = OpIAdd %uint %59 %uint_9
+%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_9
+%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_6
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %90
+OpStore %91 %50
+%93 = OpIAdd %uint %59 %uint_7
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %93
+OpStore %94 %51
+%96 = OpIAdd %uint %59 %uint_8
+%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
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, InstBoundsInitLoadSizedUBOArray) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer[128];
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer[nu_ii].a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %18 NonUniformEXT
+OpDecorate %22 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %89 NonUniformEXT
+OpDecorate %120 NonUniformEXT
+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 NonUniformEXT
+%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_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%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_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%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
+%5 = OpLabel
+%18 = OpLoad %int %nu_ii
+%21 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %18 %int_0
+%22 = OpLoad %float %21
+OpStore %b %22
+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 = OpINotEqual %bool %112 %uint_0
+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_9
+%47 = OpIAdd %uint %46 %uint_9
+%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_9
+%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_6
+%79 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %78
+OpStore %79 %34
+%81 = OpIAdd %uint %46 %uint_7
+%82 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %81
+OpStore %82 %35
+%84 = OpIAdd %uint %46 %uint_8
+%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
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, SimpleV2) {
+  // Texture2D g_tColor[128];
+  //
+  // layout(push_constant) cbuffer PerViewConstantBuffer_t
+  // {
+  //   uint g_nDataIdx;
+  // };
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   ps_output.vColor =
+  //       g_tColor[ g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   return ps_output;
+  // }
+
+  const std::string entry_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+)";
+
+  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"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+  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
+%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_128 = OpConstant %uint 128
+%_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
+%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
+)";
+
+  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
+%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
+)";
+
+  const std::string func_pt2_before =
+      R"(%37 = OpImageSampleImplicitLod %v4float %36 %30
+OpStore %_entryPointOutput_vColor %37
+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
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass, uint32_t, uint32_t, bool, bool,
+                        uint32_t>(
+      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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentMultipleInstructionsV2) {
+  // Texture2D g_tColor[128];
+  //
+  // layout(push_constant) cbuffer PerViewConstantBuffer_t
+  // {
+  //   uint g_nDataIdx;
+  //   uint g_nDataIdx2;
+  // };
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //
+  //   float t  = g_tColor[g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   float t2 = g_tColor[g_nDataIdx2].Sample(g_sAniso, i.vTextureCoords.xy);
+  //   ps_output.vColor = t + t2;
+  //   return ps_output;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %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
+%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
+)";
+
+  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 =
+      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
+%38 = OpImageSampleImplicitLod %v4float %37 %31
+%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
+%44 = OpImageSampleImplicitLod %v4float %43 %31
+%45 = OpFAdd %v4float %38 %44
+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
+)";
+
+  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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentOpImageV2) {
+  // This test verifies that the pass will correctly instrument shader
+  // using OpImage. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %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
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_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_39 = OpTypePointer UniformConstant %39
+%_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
+)";
+
+  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
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%75 = OpImage %20 %66
+%71 = OpImageRead %v4float %75 %53
+OpStore %_entryPointOutput_vColor %71
+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 =
+      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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentSampledImageV2) {
+  // This test verifies that the pass will correctly instrument shader
+  // using sampled image. This test was created by editing the SPIR-V
+  // from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %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
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_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_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+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
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%71 = OpImageSampleImplicitLod %v4float %66 %53
+OpStore %_entryPointOutput_vColor %71
+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 =
+      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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentImageWriteV2) {
+  // This test verifies that the pass will correctly instrument shader
+  // 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
+OpCapability StorageImageWriteWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %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
+%void = OpTypeVoid
+%3 = 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
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%80 = OpConstantNull %v4float
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%_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
+)";
+
+  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
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+OpImageWrite %66 %53 %80
+OpStore %_entryPointOutput_vColor %80
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentVertexSimpleV2) {
+  // This test verifies that the pass will correctly instrument shader
+  // 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
+OpCapability Sampled1D
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D
+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
+%void = OpTypeVoid
+%3 = 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
+%21 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%22 = OpTypeSampledImage %21
+%uint_128 = OpConstant %uint 128
+%_arr_22_uint_128 = OpTypeArray %22 %uint_128
+%_ptr_UniformConstant__arr_22_uint_128 = OpTypePointer UniformConstant %_arr_22_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_22_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_22 = OpTypePointer UniformConstant %22
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+)";
+
+  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
+%5 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%31 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%32 = OpLoad %int %31
+%34 = OpAccessChain %_ptr_UniformConstant_22 %texSampler1D %32
+%35 = OpLoad %22 %34
+%36 = OpLoad %float %coords1D
+%37 = OpLoad %float %lod
+%38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
+%40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %40 %38
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, MultipleDebugFunctionsV2) {
+  // Same source as Simple, but compiled -g and not optimized, especially not
+  // inlined. The OpSource has had the source extracted for the sake of brevity.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%2 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%1 = OpString "foo5.frag"
+OpSource HLSL 500 %1
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%13 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_21_uint_128 = OpTypeArray %21 %uint_128
+%_ptr_UniformConstant__arr_21_uint_128 = OpTypePointer UniformConstant %_arr_21_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_21_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%36 = OpTypeSampler
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%g_sAniso = OpVariable %_ptr_UniformConstant_36 UniformConstant
+%40 = OpTypeSampledImage %21
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo5.frag"
+OpSource HLSL 500 %5
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_77 Block
+OpMemberDecorate %_struct_77 0 Offset 0
+OpMemberDecorate %_struct_77 1 Offset 4
+OpDecorate %79 DescriptorSet 7
+OpDecorate %79 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%18 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%23 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%27 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_27_uint_128 = OpTypeArray %27 %uint_128
+%_ptr_UniformConstant__arr_27_uint_128 = OpTypePointer UniformConstant %_arr_27_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_27_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%37 = OpTypeSampledImage %27
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%70 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_77 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_77 = OpTypePointer StorageBuffer %_struct_77
+%79 = OpVariable %_ptr_StorageBuffer__struct_77 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_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_93 = OpConstant %uint 93
+%125 = OpConstantNull %v4float
+)";
+
+  const std::string func1_before =
+      R"(%MainPs = OpFunction %void None %4
+%6 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %1 21 0
+%54 = OpLoad %v2float %i_vTextureCoords
+%55 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %55 %54
+%59 = OpLoad %PS_INPUT %i_0
+OpStore %param %59
+%60 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%61 = OpCompositeExtract %v4float %60 0
+OpStore %_entryPointOutput_vColor %61
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func1_after =
+      R"(%MainPs = OpFunction %void None %18
+%42 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 21 0
+%43 = OpLoad %v2float %i_vTextureCoords
+%44 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %44 %43
+%45 = OpLoad %PS_INPUT %i_0
+OpStore %param %45
+%46 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%47 = OpCompositeExtract %v4float %46 0
+OpStore %_entryPointOutput_vColor %47
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func2_before =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %13
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%16 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %1 24 0
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%34 = OpAccessChain %_ptr_UniformConstant_21 %g_tColor %32
+%35 = OpLoad %21 %34
+%39 = OpLoad %36 %g_sAniso
+%41 = OpSampledImage %40 %35 %39
+%43 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %41 %44
+%47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %47 %45
+OpLine %1 25 0
+%48 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %48
+OpFunctionEnd
+)";
+
+  const std::string func2_after =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %23
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%48 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 24 0
+%49 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%50 = OpLoad %uint %49
+%51 = OpAccessChain %_ptr_UniformConstant_27 %g_tColor %50
+%52 = OpLoad %27 %51
+%53 = OpLoad %35 %g_sAniso
+%54 = OpSampledImage %37 %52 %53
+%55 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%56 = OpLoad %v2float %55
+%62 = OpULessThan %bool %50 %uint_128
+OpSelectionMerge %63 None
+OpBranchConditional %62 %64 %65
+%64 = OpLabel
+%66 = OpLoad %27 %51
+%67 = OpSampledImage %37 %66 %53
+%68 = OpImageSampleImplicitLod %v4float %67 %56
+OpBranch %63
+%65 = OpLabel
+%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+OpBranch %63
+%63 = OpLabel
+%126 = OpPhi %v4float %68 %64 %125 %65
+%58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %58 %126
+OpLine %5 25 0
+%59 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %59
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%69 = OpFunction %void None %70
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpFunctionParameter %uint
+%74 = OpFunctionParameter %uint
+%75 = OpLabel
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_0
+%84 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, RuntimeArrayV2) {
+  // This test verifies that the pass will correctly instrument shader
+  // with runtime descriptor array. This test was created by editing the
+  // SPIR-V from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_rarr_20 = OpTypeRuntimeArray %20
+%_ptr_UniformConstant__arr_20 = OpTypePointer UniformConstant %_rarr_20
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_46 Block
+OpMemberDecorate %_struct_46 0 Offset 0
+OpDecorate %48 DescriptorSet 7
+OpDecorate %48 Binding 1
+OpDecorate %_struct_71 Block
+OpMemberDecorate %_struct_71 0 Offset 0
+OpMemberDecorate %_struct_71 1 Offset 4
+OpDecorate %73 DescriptorSet 7
+OpDecorate %73 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_runtimearr_16 = OpTypeRuntimeArray %16
+%_ptr_UniformConstant__runtimearr_16 = OpTypePointer UniformConstant %_runtimearr_16
+%g_tColor = OpVariable %_ptr_UniformConstant__runtimearr_16 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%uint_2 = OpConstant %uint 2
+%41 = OpTypeFunction %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_46 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46
+%48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%65 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_71 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_71 = OpTypePointer StorageBuffer %_struct_71
+%73 = OpVariable %_ptr_StorageBuffer__struct_71 StorageBuffer
+%uint_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
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+%67 = OpLoad %35 %g_sAniso
+%68 = OpSampledImage %39 %66 %67
+%71 = OpImageSampleImplicitLod %v4float %68 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+%55 = OpFunctionCall %uint %40 %uint_2 %uint_2
+%57 = OpULessThan %bool %32 %55
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+%59 = OpLabel
+%61 = OpLoad %16 %33
+%62 = OpSampledImage %26 %61 %35
+%136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32
+%137 = OpINotEqual %bool %136 %uint_0
+OpSelectionMerge %138 None
+OpBranchConditional %137 %139 %140
+%139 = OpLabel
+%141 = OpLoad %16 %33
+%142 = OpSampledImage %26 %141 %35
+%143 = OpImageSampleImplicitLod %v4float %142 %30
+OpBranch %138
+%140 = OpLabel
+%144 = OpFunctionCall %void %64 %uint_59 %uint_1 %32 %uint_0
+OpBranch %138
+%138 = OpLabel
+%145 = OpPhi %v4float %143 %139 %116 %140
+OpBranch %58
+%60 = OpLabel
+%115 = OpFunctionCall %void %64 %uint_59 %uint_0 %32 %55
+OpBranch %58
+%58 = OpLabel
+%117 = OpPhi %v4float %145 %138 %116 %60
+OpStore %_entryPointOutput_vColor %117
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string new_funcs =
+      R"(%40 = OpFunction %uint None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpLabel
+%50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %42
+%51 = OpLoad %uint %50
+%52 = OpIAdd %uint %51 %43
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %52
+%54 = OpLoad %uint %53
+OpReturnValue %54
+OpFunctionEnd
+%64 = OpFunction %void None %65
+%66 = OpFunctionParameter %uint
+%67 = OpFunctionParameter %uint
+%68 = OpFunctionParameter %uint
+%69 = OpFunctionParameter %uint
+%70 = OpLabel
+%74 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_0
+%77 = OpAtomicIAdd %uint %74 %uint_4 %uint_0 %uint_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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptorV2) {
+  // This test verifies that the pass will correctly instrument vanilla
+  // texture sample on a scalar descriptor with an initialization check if the
+  // input_init_enable argument is set to true. This can happen when the
+  // descriptor indexing extension is enabled in the API but the SPIR-V
+  // 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
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_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
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%24 = OpImageSampleImplicitLod %v4float %23 %20
+OpStore %_entryPointOutput_vColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0
+%52 = OpINotEqual %bool %50 %uint_0
+OpSelectionMerge %54 None
+OpBranchConditional %52 %55 %56
+%55 = OpLabel
+%57 = OpLoad %12 %g_tColor
+%58 = OpSampledImage %16 %57 %22
+%59 = OpImageSampleImplicitLod %v4float %58 %20
+OpBranch %54
+%56 = OpLabel
+%112 = OpFunctionCall %void %60 %uint_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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, SPV14AddToEntryPointV2) {
+  const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "foo" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpDecorate [[v1]] DescriptorSet 7
+; CHECK: OpDecorate [[v2]] DescriptorSet 7
+; CHECK: [[v1]] = OpVariable {{%\w+}} StorageBuffer
+; CHECK: [[v2]] = OpVariable {{%\w+}} StorageBuffer
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %foo "foo" %gid %image_var %sampler_var
+OpExecutionMode %foo OriginUpperLeft
+OpDecorate %image_var DescriptorSet 0
+OpDecorate %image_var Binding 0
+OpDecorate %sampler_var DescriptorSet 0
+OpDecorate %sampler_var Binding 1
+OpDecorate %gid DescriptorSet 0
+OpDecorate %gid Binding 2
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%v3int = OpTypeVector %int 3
+%float = OpTypeFloat 32
+%v3float = OpTypeVector %float 3
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %v3int
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ptr_ssbo_v3int = OpTypePointer StorageBuffer %v3int
+%gid = OpVariable %ptr_ssbo_struct StorageBuffer
+%image = OpTypeImage %float 3D 0 0 0 1 Unknown
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%sampled = OpTypeSampledImage %image
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+%gep = OpAccessChain %ptr_ssbo_v3int %gid %int_0
+%ld_gid = OpLoad %v3int %gep
+%convert = OpConvertUToF %v3float %ld_gid
+%sampled_image = OpSampledImage %sampled %ld_image %ld_sampler
+%sample = OpImageSampleImplicitLod %v4float %sampled_image %convert
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, true, true,
+                                               2u);
+}
+
+TEST_F(InstBindlessTest, SPV14AddToEntryPointsV2) {
+  const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "foo" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpEntryPoint Fragment {{%\w+}} "bar" {{%\w+}} {{%\w+}} {{%\w+}} [[v1:%\w+]] [[v2:%\w+]]
+; CHECK: OpDecorate [[v1]] DescriptorSet 7
+; CHECK: OpDecorate [[v2]] DescriptorSet 7
+; CHECK: [[v1]] = OpVariable {{%\w+}} StorageBuffer
+; CHECK: [[v2]] = OpVariable {{%\w+}} StorageBuffer
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %foo "foo" %gid %image_var %sampler_var
+OpEntryPoint Fragment %foo "bar" %gid %image_var %sampler_var
+OpExecutionMode %foo OriginUpperLeft
+OpDecorate %image_var DescriptorSet 0
+OpDecorate %image_var Binding 0
+OpDecorate %sampler_var DescriptorSet 0
+OpDecorate %sampler_var Binding 1
+OpDecorate %gid DescriptorSet 0
+OpDecorate %gid Binding 2
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%v3int = OpTypeVector %int 3
+%float = OpTypeFloat 32
+%v3float = OpTypeVector %float 3
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %v3int
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ptr_ssbo_v3int = OpTypePointer StorageBuffer %v3int
+%gid = OpVariable %ptr_ssbo_struct StorageBuffer
+%image = OpTypeImage %float 3D 0 0 0 1 Unknown
+%ptr_uc_image = OpTypePointer UniformConstant %image
+%sampler = OpTypeSampler
+%ptr_uc_sampler = OpTypePointer UniformConstant %sampler
+%image_var = OpVariable %ptr_uc_image UniformConstant
+%sampler_var = OpVariable %ptr_uc_sampler UniformConstant
+%sampled = OpTypeSampledImage %image
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld_image = OpLoad %image %image_var
+%ld_sampler = OpLoad %sampler %sampler_var
+%gep = OpAccessChain %ptr_ssbo_v3int %gid %int_0
+%ld_gid = OpLoad %v3int %gep
+%convert = OpConvertUToF %v3float %ld_gid
+%sampled_image = OpSampledImage %sampled %ld_image %ld_sampler
+%sample = OpImageSampleImplicitLod %v4float %sampled_image %convert
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, true, true,
+                                               2u);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedUBOArrayV2) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer[];
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer[nu_ii].a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 = OpINotEqual %bool %122 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArrayDeprecatedV2) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  buffer bname { float b; }  storageBuffer[];
+  //
+  // void main()
+  // {
+  //     b = storageBuffer[nu_ii].b;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 = OpINotEqual %bool %122 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArrayV2) {
+  // Same as Deprecated but declaring as StorageBuffer Block
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %16 NonUniformEXT
+OpDecorate %20 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %102 NonUniformEXT
+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 NonUniformEXT
+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 NonUniformEXT
+%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
+%5 = OpLabel
+%16 = OpLoad %int %nu_ii
+%19 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %16 %int_0
+%20 = OpLoad %float %19
+OpStore %b %20
+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 = OpINotEqual %bool %122 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstInitLoadUBOScalarV2) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) out float b;
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer;
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer.a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b
+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
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  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
+%5 = OpLabel
+%15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0
+%16 = OpLoad %float %15
+OpStore %b %16
+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 = OpINotEqual %bool %43 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstBoundsInitStoreUnsizedSSBOArrayV2) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=1) in float b;
+  //
+  // layout(binding=4)  buffer bname { float b; }  storageBuffer[];
+  //
+  // void main()
+  // {
+  //     storageBuffer[nu_ii].b = b;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %nu_ii %b
+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 NonUniformEXT
+OpDecorate %14 NonUniformEXT
+OpDecorate %b Location 1
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability StorageBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+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
+%5 = OpLabel
+%14 = OpLoad %int %nu_ii
+%18 = OpLoad %float %b
+%20 = OpAccessChain %_ptr_Uniform_float %storageBuffer %14 %int_0
+OpStore %20 %18
+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 = OpINotEqual %bool %119 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest, InstBoundsInitLoadSizedUBOArrayV2) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout(location=0) in nonuniformEXT flat int nu_ii;
+  // layout(location=0) out float b;
+  //
+  // layout(binding=3)  uniform uname { float a; }  uniformBuffer[128];
+  //
+  // void main()
+  // {
+  //     b = uniformBuffer[nu_ii].a;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %b %nu_ii
+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 NonUniformEXT
+OpDecorate %18 NonUniformEXT
+OpDecorate %22 NonUniformEXT
+%void = OpTypeVoid
+%3 = 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
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+OpCapability UniformBufferArrayNonUniformIndexingEXT
+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 NonUniformEXT
+OpDecorate %7 NonUniformEXT
+OpDecorate %89 NonUniformEXT
+OpDecorate %120 NonUniformEXT
+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 NonUniformEXT
+%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
+%5 = OpLabel
+%18 = OpLoad %int %nu_ii
+%21 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %18 %int_0
+%22 = OpLoad %float %21
+OpStore %b %22
+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 = OpINotEqual %bool %112 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsComputeShaderInitLoadVariableSizedSampledImagesArray) {
+  // #version 450
+  // #extension GL_EXT_nonuniform_qualifier : enable
+  //
+  // layout (local_size_x = 1, local_size_y = 1) in;
+  //
+  // layout(set = 0, binding = 0, std140) buffer Input {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+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
+%void = OpTypeVoid
+%3 = 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
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint 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
+%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
+)";
+
+  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 = OpINotEqual %bool %132 %uint_0
+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 = OpINotEqual %bool %142 %uint_0
+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 = OpINotEqual %bool %151 %uint_0
+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
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsRayGenerationInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsIntersectionInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint IntersectionNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsAnyHitInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint AnyHitNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsClosestHitInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint ClosestHitNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsMissInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint MissNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
+TEST_F(InstBindlessTest,
+       InstBoundsCallableInitLoadVariableSizedSampledImagesArray) {
+  // #version 460
+  // #extension GL_EXT_nonuniform_qualifier : require
+  // #extension GL_NV_ray_tracing : require
+  //
+  // layout(set = 0, binding = 0, std140) buffer StorageBuffer {
+  //   uint index;
+  //   float red;
+  // } sbo;
+  //
+  // layout(set = 0, binding = 1, rgba32f) readonly uniform image2D images[];
+  //
+  // void main()
+  // {
+  //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
+  // }
+
+  const std::string defs_before =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+OpCapability RayTracingNV
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_NV_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint CallableNV %main "main"
+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
+%void = OpTypeVoid
+)";
+
+  const std::string defs_after =
+      R"(OpCapability RuntimeDescriptorArrayEXT
+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
+%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
+%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
+%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
+)";
+
+  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 = OpINotEqual %bool %133 %uint_0
+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 = OpINotEqual %bool %143 %uint_0
+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 = OpINotEqual %bool %152 %uint_0
+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
+)";
+
+  // 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, 2u);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
-// TODO(greg-lunarg): Come up with cases to put here :)
+//   Compute shader
+//   Geometry shader
+//   Tesselation control shader
+//   Tesselation eval shader
+//   OpImage
+//   SampledImage variable
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/legalize_vector_shuffle_test.cpp b/test/opt/legalize_vector_shuffle_test.cpp
new file mode 100644
index 0000000..8b9695b
--- /dev/null
+++ b/test/opt/legalize_vector_shuffle_test.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using LegalizeVectorShuffleTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+void operator+=(std::vector<const char*>& lhs,
+                const std::vector<const char*> rhs) {
+  for (auto elem : rhs) lhs.push_back(elem);
+}
+
+std::vector<const char*> header = {
+    "OpCapability Shader",
+    "OpCapability VulkanMemoryModelKHR",
+    "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+    "OpMemoryModel Logical VulkanKHR",
+    "OpEntryPoint Vertex %1 \"shader\"",
+    "%uint = OpTypeInt 32 0",
+    "%v3uint = OpTypeVector %uint 3"};
+
+std::string GetTestString(const char* shuffle) {
+  std::vector<const char*> result = header;
+  result += {"%_ptr_Function_v3uint = OpTypePointer Function %v3uint",
+             "%void = OpTypeVoid",
+             "%6 = OpTypeFunction %void",
+             "%1 = OpFunction %void None %6",
+             "%7 = OpLabel",
+             "%8 = OpVariable %_ptr_Function_v3uint Function",
+             "%9 = OpLoad %v3uint %8",
+             "%10 = OpLoad %v3uint %8"};
+  result += shuffle;
+  result += {"OpReturn", "OpFunctionEnd"};
+  return JoinAllInsts(result);
+}
+
+TEST_F(LegalizeVectorShuffleTest, Changed) {
+  std::string input =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0xFFFFFFFF");
+  std::string expected =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+
+  SinglePassRunAndCheck<LegalizeVectorShufflePass>(input, expected,
+                                                   /* skip_nop = */ false);
+}
+
+TEST_F(LegalizeVectorShuffleTest, FunctionUnchanged) {
+  std::string input =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+  std::string expected =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+
+  SinglePassRunAndCheck<LegalizeVectorShufflePass>(input, expected,
+                                                   /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 154824b..1b75b36 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -700,6 +700,126 @@
                                                      true);
 }
 
+TEST_F(LocalAccessChainConvertTest, VariablePointersStorageBuffer) {
+  // A case with a storage buffer variable pointer.  We should still convert
+  // the access chain on the function scope symbol.
+  const std::string test =
+      R"(
+; CHECK: OpFunction
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Function
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[var]]
+; CHECK: OpCompositeExtract {{%\w+}} [[ld]] 0 0
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_struct_3 Block
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 0
+               OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+               OpDecorate %_arr_int_int_128 ArrayStride 4
+       %void = OpTypeVoid
+          %8 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+    %int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+  %_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+          %4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%_ptr_Function_int = OpTypePointer Function %int
+          %2 = OpFunction %void None %8
+         %18 = OpLabel
+         %19 = OpVariable %_ptr_Function__struct_3 Function
+         %20 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+               OpBranch %21
+         %21 = OpLabel
+         %22 = OpPhi %_ptr_StorageBuffer_int %20 %18 %23 %24
+               OpLoopMerge %25 %24 None
+               OpBranchConditional %true %26 %25
+         %26 = OpLabel
+               OpStore %22 %int_0
+               OpBranch %24
+         %24 = OpLabel
+         %23 = OpPtrAccessChain %_ptr_StorageBuffer_int %22 %int_1
+               OpBranch %21
+         %25 = OpLabel
+         %27 = OpAccessChain %_ptr_Function_int %19 %int_0 %int_0
+         %28 = OpLoad %int %27
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(test, true);
+}
+
+TEST_F(LocalAccessChainConvertTest, VariablePointers) {
+  // A case with variable pointer capability.  We should not convert
+  // the access chain on the function scope symbol because the variable pointer
+  // could the analysis to miss references to function scope symbols.
+  const std::string test =
+      R"(OpCapability Shader
+OpCapability VariablePointers
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "main"
+OpExecutionMode %2 LocalSize 1 1 1
+OpSource GLSL 450
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_struct_3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+OpDecorate %_arr_int_int_128 ArrayStride 4
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+%_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%_ptr_Function_int = OpTypePointer Function %int
+%2 = OpFunction %void None %8
+%18 = OpLabel
+%19 = OpVariable %_ptr_Function__struct_3 Function
+%20 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %_ptr_StorageBuffer_int %20 %18 %23 %24
+OpLoopMerge %25 %24 None
+OpBranchConditional %true %26 %25
+%26 = OpLabel
+OpStore %22 %int_0
+OpBranch %24
+%24 = OpLabel
+%23 = OpPtrAccessChain %_ptr_StorageBuffer_int %22 %int_1
+OpBranch %21
+%25 = OpLabel
+%27 = OpAccessChain %_ptr_Function_int %19 %int_0 %int_0
+%28 = OpLoad %int %27
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(test, test, false, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Assorted vector and matrix types
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp
index e2efc5e..402352d 100644
--- a/test/opt/local_single_block_elim.cpp
+++ b/test/opt/local_single_block_elim.cpp
@@ -1062,6 +1062,62 @@
   SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
       predefs + before, predefs + after, true, true);
 }
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, VariablePointerTest) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %15 %int_1
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+               OpStore %17 %int_0
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalSingleBlockLoadStoreElimPass>(text, false);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Other target variable types
diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp
index 5fd0217..5a1650b 100644
--- a/test/opt/local_single_store_elim_test.cpp
+++ b/test/opt/local_single_store_elim_test.cpp
@@ -847,6 +847,61 @@
   SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
                                                   predefs + after, true, true);
 }
+
+TEST_F(LocalSingleStoreElimTest, VariablePointerTest) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Other types
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
index b2961a2..a490d52 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -1820,6 +1820,173 @@
   SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
 }
 
+TEST_F(LocalSSAElimTest, VariablePointerTest1) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<SSARewritePass>(text, false);
+}
+
+TEST_F(LocalSSAElimTest, VariablePointerTest2) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalMultiStoreElimPass>(text, false);
+}
+
+TEST_F(LocalSSAElimTest, ChainedTrivialPhis) {
+  // Check that the copy object get the undef value implicitly assigned in the
+  // entry block.
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef %v4float
+; CHECK: OpCopyObject %v4float [[undef]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 18 6
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+          %2 = OpFunction %void None %4
+          %9 = OpLabel
+         %10 = OpVariable %_ptr_Function_v4float Function
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpUndef %bool
+               OpBranchConditional %15 %16 %12
+         %16 = OpLabel
+         %17 = OpUndef %bool
+               OpSelectionMerge %18 None
+               OpBranchConditional %17 %19 %18
+         %19 = OpLabel
+         %20 = OpUndef %bool
+               OpLoopMerge %21 %22 None
+               OpBranchConditional %20 %23 %21
+         %23 = OpLabel
+         %24 = OpLoad %v4float %10
+         %25 = OpCopyObject %v4float %24
+         %26 = OpUndef %bool
+               OpBranch %22
+         %22 = OpLabel
+               OpBranch %19
+         %21 = OpLabel
+               OpBranch %12
+         %18 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+         %27 = OpLoad %v4float %10
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<SSARewritePass>(text, false);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp
index 90abc00..ee6e949 100644
--- a/test/opt/optimizer_test.cpp
+++ b/test/opt/optimizer_test.cpp
@@ -163,7 +163,6 @@
       "--eliminate-dead-branches",
       "--eliminate-dead-functions",
       "--eliminate-local-multi-store",
-      "--eliminate-common-uniform",
       "--eliminate-dead-const",
       "--eliminate-dead-inserts",
       "--eliminate-dead-variables",
@@ -222,6 +221,528 @@
   EXPECT_EQ(msg_level, SPV_MSG_ERROR);
 }
 
+TEST(Optimizer, VulkanToWebGPUSetsCorrectPasses) {
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterVulkanToWebGPUPasses();
+  std::vector<const char*> pass_names = opt.GetPassNames();
+
+  std::vector<std::string> registered_passes;
+  for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
+    registered_passes.push_back(*name);
+
+  std::vector<std::string> expected_passes = {"eliminate-dead-branches",
+                                              "eliminate-dead-code-aggressive",
+                                              "eliminate-dead-const",
+                                              "flatten-decorations",
+                                              "strip-debug",
+                                              "strip-atomic-counter-memory",
+                                              "generate-webgpu-initializers",
+                                              "legalize-vector-shuffle",
+                                              "split-invalid-unreachable",
+                                              "compact-ids"};
+  std::sort(registered_passes.begin(), registered_passes.end());
+  std::sort(expected_passes.begin(), expected_passes.end());
+
+  ASSERT_EQ(registered_passes.size(), expected_passes.size());
+  for (size_t i = 0; i < registered_passes.size(); i++)
+    EXPECT_EQ(registered_passes[i], expected_passes[i]);
+}
+
+struct VulkanToWebGPUPassCase {
+  // Input SPIR-V
+  std::string input;
+  // Expected result SPIR-V
+  std::string expected;
+  // Specific pass under test, used for logging messages.
+  std::string pass;
+};
+
+using VulkanToWebGPUPassTest =
+    PassTest<::testing::TestWithParam<VulkanToWebGPUPassCase>>;
+
+TEST_P(VulkanToWebGPUPassTest, Ran) {
+  std::vector<uint32_t> binary;
+  {
+    SpirvTools tools(SPV_ENV_VULKAN_1_1);
+    tools.Assemble(GetParam().input, &binary);
+  }
+
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterVulkanToWebGPUPasses();
+
+  std::vector<uint32_t> optimized;
+  class ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+  std::string disassembly;
+  {
+    SpirvTools tools(SPV_ENV_WEBGPU_0);
+    tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+  }
+
+  EXPECT_EQ(GetParam().expected, disassembly)
+      << "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Optimizer, VulkanToWebGPUPassTest,
+    ::testing::ValuesIn(std::vector<VulkanToWebGPUPassCase>{
+        // FlattenDecorations
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %main \"main\" %hue %saturation %value\n"
+         "OpExecutionMode %main OriginUpperLeft\n"
+         "OpDecorate %group Flat\n"
+         "OpDecorate %group NoPerspective\n"
+         "%group = OpDecorationGroup\n"
+         "%void = OpTypeVoid\n"
+         "%void_fn = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%hue = OpVariable %_ptr_Input_float Input\n"
+         "%saturation = OpVariable %_ptr_Input_float Input\n"
+         "%value = OpVariable %_ptr_Input_float Input\n"
+         "%main = OpFunction %void None %void_fn\n"
+         "%entry = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %1 \"main\" %2 %3 %4\n"
+         "OpExecutionMode %1 OriginUpperLeft\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%2 = OpVariable %_ptr_Input_float Input\n"
+         "%3 = OpVariable %_ptr_Input_float Input\n"
+         "%4 = OpVariable %_ptr_Input_float Input\n"
+         "%1 = OpFunction %void None %6\n"
+         "%9 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "flatten-decorations"},
+        // Strip Debug
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "OpName %main \"main\"\n"
+         "OpName %void_fn \"void_fn\"\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%void = OpTypeVoid\n"
+         "%3 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %3\n"
+         "%4 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-debug"},
+        // Eliminate Dead Constants
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%relaxed = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         "eliminate-dead-const"},
+        // Strip Atomic Counter Memory
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_0 = OpConstant %u32 0\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "%val0 = OpAtomicStore %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup %u32_1\n"
+         "%val1 = OpAtomicIIncrement %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup\n"
+         "%val2 = OpAtomicCompareExchange %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup "
+         "%acquire_release_atomic_counter_workgroup %u32_0 %u32_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%uint_0 = OpConstant %uint 0\n"
+         "%uint_1 = OpConstant %uint 1\n"
+         "%uint_0_0 = OpConstant %uint 0\n"
+         "%void = OpTypeVoid\n"
+         "%9 = OpTypeFunction %void\n"
+         "%uint_264 = OpConstant %uint 264\n"
+         "%1 = OpFunction %void None %9\n"
+         "%11 = OpLabel\n"
+         "OpAtomicStore %4 %uint_0_0 %uint_264 %uint_1\n"
+         "%12 = OpAtomicIIncrement %uint %4 %uint_0_0 %uint_264\n"
+         "%13 = OpAtomicCompareExchange %uint %4 %uint_0_0 %uint_264 %uint_264 "
+         "%uint_0 %uint_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-atomic-counter-memory"},
+        // Generate WebGPU Initializers
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Private %u32\n"
+         "%u32_var = OpVariable %u32_ptr Private\n"
+         "%u32_0 = OpConstant %u32 0\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpStore %u32_var %u32_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Private_uint = OpTypePointer Private %uint\n"
+         "%4 = OpConstantNull %uint\n"
+         "%5 = OpVariable %_ptr_Private_uint Private %4\n"
+         "%uint_0 = OpConstant %uint 0\n"
+         "%void = OpTypeVoid\n"
+         "%8 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %8\n"
+         "%9 = OpLabel\n"
+         "OpStore %5 %uint_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "generate-webgpu-initializers"},
+        // Legalize Vector Shuffle
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%v3uint = OpTypeVector %uint 3\n"
+         "%_ptr_Function_v3uint = OpTypePointer Function %v3uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_v3uint Function\n"
+         "%9 = OpLoad %v3uint %8\n"
+         "%10 = OpLoad %v3uint %8\n"
+         "%11 = OpVectorShuffle %v3uint %9 %10 2 1 0xFFFFFFFF\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%v3uint = OpTypeVector %uint 3\n"
+         "%_ptr_Function_v3uint = OpTypePointer Function %v3uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%7 = OpConstantNull %v3uint\n"
+         "%1 = OpFunction %void None %6\n"
+         "%8 = OpLabel\n"
+         "%9 = OpVariable %_ptr_Function_v3uint Function %7\n"
+         "%10 = OpLoad %v3uint %9\n"
+         "%11 = OpLoad %v3uint %9\n"
+         "%12 = OpVectorShuffle %v3uint %10 %11 2 1 0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "legalize-vector-shuffle"},
+        // Split Invalid Unreachable
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%uint_1 = OpConstant %uint 1\n"
+         "%uint_2 = OpConstant %uint 2\n"
+         "%void = OpTypeVoid\n"
+         "%bool = OpTypeBool\n"
+         "%7 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %7\n"
+         "%8 = OpLabel\n"
+         "OpBranch %9\n"
+         "%9 = OpLabel\n"
+         "OpLoopMerge %10 %11 None\n"
+         "OpBranch %12\n"
+         "%12 = OpLabel\n"
+         "%13 = OpSLessThan %bool %uint_1 %uint_2\n"
+         "OpSelectionMerge %11 None\n"
+         "OpBranchConditional %13 %14 %15\n"
+         "%14 = OpLabel\n"
+         "OpReturn\n"
+         "%15 = OpLabel\n"
+         "OpReturn\n"
+         "%10 = OpLabel\n"
+         "OpUnreachable\n"
+         "%11 = OpLabel\n"
+         "OpBranch %9\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%uint_1 = OpConstant %uint 1\n"
+         "%uint_2 = OpConstant %uint 2\n"
+         "%void = OpTypeVoid\n"
+         "%bool = OpTypeBool\n"
+         "%7 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %7\n"
+         "%8 = OpLabel\n"
+         "OpBranch %9\n"
+         "%9 = OpLabel\n"
+         "OpLoopMerge %10 %11 None\n"
+         "OpBranch %12\n"
+         "%12 = OpLabel\n"
+         "%13 = OpSLessThan %bool %uint_1 %uint_2\n"
+         "OpSelectionMerge %14 None\n"
+         "OpBranchConditional %13 %15 %16\n"
+         "%15 = OpLabel\n"
+         "OpReturn\n"
+         "%16 = OpLabel\n"
+         "OpReturn\n"
+         "%10 = OpLabel\n"
+         "OpUnreachable\n"
+         "%14 = OpLabel\n"
+         "OpUnreachable\n"
+         "%11 = OpLabel\n"
+         "OpBranch %9\n"
+         "OpFunctionEnd\n",
+         // pass
+         "split-invalid-unreachable"},
+        // Compact IDs
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1000 \"shader\"\n"
+         "%10 = OpTypeVoid\n"
+         "%100 = OpTypeFunction %10\n"
+         "%1000 = OpFunction %10 None %100\n"
+         "%10000 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%void = OpTypeVoid\n"
+         "%3 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %3\n"
+         "%4 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "compact-ids"}}));
+
+TEST(Optimizer, WebGPUToVulkanSetsCorrectPasses) {
+  Optimizer opt(SPV_ENV_VULKAN_1_1);
+  opt.RegisterWebGPUToVulkanPasses();
+  std::vector<const char*> pass_names = opt.GetPassNames();
+
+  std::vector<std::string> registered_passes;
+  for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
+    registered_passes.push_back(*name);
+
+  std::vector<std::string> expected_passes = {"decompose-initialized-variables",
+                                              "compact-ids"};
+  std::sort(registered_passes.begin(), registered_passes.end());
+  std::sort(expected_passes.begin(), expected_passes.end());
+
+  ASSERT_EQ(registered_passes.size(), expected_passes.size());
+  for (size_t i = 0; i < registered_passes.size(); i++)
+    EXPECT_EQ(registered_passes[i], expected_passes[i]);
+}
+
+struct WebGPUToVulkanPassCase {
+  // Input SPIR-V
+  std::string input;
+  // Expected result SPIR-V
+  std::string expected;
+  // Specific pass under test, used for logging messages.
+  std::string pass;
+};
+
+using WebGPUToVulkanPassTest =
+    PassTest<::testing::TestWithParam<WebGPUToVulkanPassCase>>;
+
+TEST_P(WebGPUToVulkanPassTest, Ran) {
+  std::vector<uint32_t> binary;
+  {
+    SpirvTools tools(SPV_ENV_WEBGPU_0);
+    tools.Assemble(GetParam().input, &binary);
+  }
+
+  Optimizer opt(SPV_ENV_VULKAN_1_1);
+  opt.RegisterWebGPUToVulkanPasses();
+
+  std::vector<uint32_t> optimized;
+  class ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+  std::string disassembly;
+  {
+    SpirvTools tools(SPV_ENV_VULKAN_1_1);
+    tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+  }
+
+  EXPECT_EQ(GetParam().expected, disassembly)
+      << "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Optimizer, WebGPUToVulkanPassTest,
+    ::testing::ValuesIn(std::vector<WebGPUToVulkanPassCase>{
+        // Decompose Initialized Variables
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Function_uint = OpTypePointer Function %uint\n"
+         "%4 = OpConstantNull %uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_uint Function %4\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Function_uint = OpTypePointer Function %uint\n"
+         "%4 = OpConstantNull %uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_uint Function\n"
+         "OpStore %8 %4\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "decompose-initialized-variables"},
+        // Compact IDs
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1000 \"shader\"\n"
+         "%10 = OpTypeVoid\n"
+         "%100 = OpTypeFunction %10\n"
+         "%1000 = OpFunction %10 None %100\n"
+         "%10000 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%void = OpTypeVoid\n"
+         "%3 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %3\n"
+         "%4 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "compact-ids"}}));
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index 117bc31..b7a0742 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -27,6 +27,7 @@
 #include "source/opt/build_module.h"
 #include "source/opt/pass_manager.h"
 #include "source/opt/passes.h"
+#include "source/spirv_optimizer_options.h"
 #include "source/spirv_validator_options.h"
 #include "source/util/make_unique.h"
 #include "spirv-tools/libspirv.hpp"
@@ -49,24 +50,28 @@
             [](spv_message_level_t, const char*, const spv_position_t&,
                const char* message) { std::cerr << message << std::endl; }),
         context_(nullptr),
-        tools_(SPV_ENV_UNIVERSAL_1_3),
         manager_(new PassManager()),
         assemble_options_(SpirvTools::kDefaultAssembleOption),
-        disassemble_options_(SpirvTools::kDefaultDisassembleOption) {}
+        disassemble_options_(SpirvTools::kDefaultDisassembleOption),
+        env_(SPV_ENV_UNIVERSAL_1_3) {}
 
   // Runs the given |pass| on the binary assembled from the |original|.
   // Returns a tuple of the optimized binary and the boolean value returned
   // from pass Process() function.
   std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
       Pass* pass, const std::string& original, bool skip_nop) {
-    context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer_, original,
-                                     assemble_options_));
+    context_ =
+        std::move(BuildModule(env_, consumer_, original, assemble_options_));
     EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
                                   << original << std::endl;
     if (!context()) {
       return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
     }
 
+    context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
+    context()->set_preserve_spec_constants(
+        OptimizerOptions()->preserve_spec_constants_);
+
     const auto status = pass->Run(context());
 
     std::vector<uint32_t> binary;
@@ -97,8 +102,7 @@
     std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
         assembly, skip_nop, std::forward<Args>(args)...);
     if (do_validation) {
-      spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
-      spv_context spvContext = spvContextCreate(target_env);
+      spv_context spvContext = spvContextCreate(env_);
       spv_diagnostic diagnostic = nullptr;
       spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
       spv_result_t error = spvValidateWithOptions(
@@ -109,8 +113,9 @@
       spvContextDestroy(spvContext);
     }
     std::string optimized_asm;
+    SpirvTools tools(env_);
     EXPECT_TRUE(
-        tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+        tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
         << "Disassembling failed for shader:\n"
         << assembly << std::endl;
     return std::make_tuple(optimized_asm, status);
@@ -134,8 +139,7 @@
     EXPECT_EQ(original == expected,
               status == Pass::Status::SuccessWithoutChange);
     if (do_validation) {
-      spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
-      spv_context spvContext = spvContextCreate(target_env);
+      spv_context spvContext = spvContextCreate(env_);
       spv_diagnostic diagnostic = nullptr;
       spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
       spv_result_t error = spvValidateWithOptions(
@@ -146,8 +150,9 @@
       spvContextDestroy(spvContext);
     }
     std::string optimized_asm;
+    SpirvTools tools(env_);
     EXPECT_TRUE(
-        tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+        tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
         << "Disassembling failed for shader:\n"
         << original << std::endl;
     EXPECT_EQ(expected, optimized_asm);
@@ -202,17 +207,22 @@
   void RunAndCheck(const std::string& original, const std::string& expected) {
     assert(manager_->NumPasses());
 
-    context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, original,
-                                     assemble_options_));
+    context_ =
+        std::move(BuildModule(env_, nullptr, original, assemble_options_));
     ASSERT_NE(nullptr, context());
 
+    context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
+    context()->set_preserve_spec_constants(
+        OptimizerOptions()->preserve_spec_constants_);
+
     manager_->Run(context());
 
     std::vector<uint32_t> binary;
     context()->module()->ToBinary(&binary, /* skip_nop = */ false);
 
     std::string optimized;
-    EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_));
+    SpirvTools tools(env_);
+    EXPECT_TRUE(tools.Disassemble(binary, &optimized, disassemble_options_));
     EXPECT_EQ(expected, optimized);
   }
 
@@ -231,16 +241,21 @@
     consumer_ = msg_consumer;
   }
 
+  spv_optimizer_options OptimizerOptions() { return &optimizer_options_; }
+
   spv_validator_options ValidatorOptions() { return &validator_options_; }
 
+  void SetTargetEnv(spv_target_env env) { env_ = env; }
+
  private:
-  MessageConsumer consumer_;            // Message consumer.
-  std::unique_ptr<IRContext> context_;  // IR context
-  SpirvTools tools_;  // An instance for calling SPIRV-Tools functionalities.
+  MessageConsumer consumer_;              // Message consumer.
+  std::unique_ptr<IRContext> context_;    // IR context
   std::unique_ptr<PassManager> manager_;  // The pass manager.
   uint32_t assemble_options_;
   uint32_t disassemble_options_;
+  spv_optimizer_options_t optimizer_options_;
   spv_validator_options_t validator_options_;
+  spv_target_env env_;
 };
 
 }  // namespace opt
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 9c8d80d..a305680 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -1206,7 +1206,7 @@
 ; CHECK: [[bb:%\w+]] = OpLabel
 ; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
 ; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool {{%\w+}} [[old_ret_block]] [[val]] [[bb]]
 ; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK: OpBranch [[header2:%\w+]]
@@ -1263,7 +1263,7 @@
       ; CHECK: [[continue]] = OpLabel
       ; CHECK-NEXT: [[undef:%\w+]] = OpUndef
       ; CHECK: [[merge]] = OpLabel
-      ; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool [[undef]] [[continue]] {{%\w+}} {{%\w+}}
+      ; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool {{%\w+}} {{%\w+}} [[undef]] [[continue]]
       ; CHECK: OpCopyObject %bool [[phi]]
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -1318,6 +1318,118 @@
   SinglePassRunAndMatch<MergeReturnPass>(before, false);
 }
 
+TEST_F(MergeReturnPassTest, SerialLoopsUpdateBlockMapping) {
+  // #2455: This test case triggers phi insertions that use previously inserted
+  // phis. Without the fix, it fails to validate.
+  const std::string spirv = R"(
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]]
+; CHECK: [[def:%\w+]] = OpFOrdLessThan
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[def]]
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional [[phi]]
+; CHECK-NOT: [[def]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %53 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 3
+          %8 = OpTypeFunction %7
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+         %15 = OpConstant %6 1
+         %16 = OpConstantComposite %7 %15 %15 %15
+         %18 = OpTypeInt 32 1
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 1
+         %28 = OpConstant %18 0
+         %31 = OpTypePointer Function %11
+         %33 = OpConstantTrue %11
+         %51 = OpTypeVector %6 4
+         %52 = OpTypePointer Input %51
+         %53 = OpVariable %52 Input
+         %54 = OpTypeInt 32 0
+         %55 = OpConstant %54 0
+         %56 = OpTypePointer Input %6
+         %59 = OpConstant %6 0
+         %76 = OpUndef %18
+         %77 = OpUndef %11
+         %78 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %75 = OpFunctionCall %7 %9
+               OpReturn
+               OpFunctionEnd
+          %9 = OpFunction %7 None %8
+         %10 = OpLabel
+         %20 = OpVariable %19 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+         %27 = OpLoad %18 %20
+               OpLoopMerge %24 %25 None
+               OpBranch %24
+         %25 = OpLabel
+               OpBranch %22
+         %24 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %40 None
+               OpBranch %35
+         %35 = OpLabel
+               OpSelectionMerge %40 None
+               OpBranchConditional %77 %39 %40
+         %39 = OpLabel
+               OpReturnValue %16
+         %40 = OpLabel
+               OpBranchConditional %12 %34 %36
+         %36 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %45 %49 None
+               OpBranch %44
+         %44 = OpLabel
+               OpSelectionMerge %49 None
+               OpBranchConditional %77 %48 %49
+         %48 = OpLabel
+               OpReturnValue %16
+         %49 = OpLabel
+         %60 = OpFOrdLessThan %11 %15 %59
+               OpBranchConditional %12 %43 %45
+         %45 = OpLabel
+               OpBranch %62
+         %62 = OpLabel
+               OpLoopMerge %64 %68 None
+               OpBranch %63
+         %63 = OpLabel
+               OpSelectionMerge %68 None
+               OpBranchConditional %77 %67 %68
+         %67 = OpLabel
+               OpReturnValue %16
+         %68 = OpLabel
+               OpBranchConditional %60 %62 %64
+         %64 = OpLabel
+               OpReturnValue %16
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
 TEST_F(MergeReturnPassTest, InnerLoopMergeIsOuterLoopContinue) {
   const std::string before =
       R"(
@@ -1368,6 +1480,402 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<MergeReturnPass>(before, false);
 }
+
+TEST_F(MergeReturnPassTest, BreakFromLoopUseNoLongerDominated) {
+  const std::string spirv = R"(
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK-NEXT: OpBranch [[body:%\w+]]
+; CHECK: [[body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[non_ret:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret:%\w+]] [[non_ret]]
+; CHECK: [[ret]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[non_ret]] = OpLabel
+; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
+; CHECK: [[break]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret]] [[def]] [[break]] [[def]] [[cont]]
+; CHECK: OpLogicalNot {{%\w+}} [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+OpExecutionMode %func LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %8 %7 None
+OpBranch %3
+%3 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %true %4 %5
+%4 = OpLabel
+OpReturn
+%5 = OpLabel
+%def = OpLogicalNot %bool %true
+OpBranchConditional %true %6 %7
+%6 = OpLabel
+OpBranch %8
+%7 = OpLabel
+OpBranchConditional %true %2 %8
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+%use = OpLogicalNot %bool %def
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, TwoBreaksFromLoopUsesNoLongerDominated) {
+  const std::string spirv = R"(
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK-NEXT: OpBranch [[body:%\w+]]
+; CHECK: [[body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[body2:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret1:%\w+]] [[body2]]
+; CHECK: [[ret1]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[body2]] = OpLabel
+; CHECK-NEXT: [[def1:%\w+]] = OpLogicalNot
+; CHECK-NEXT: OpSelectionMerge [[body3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret2:%\w+]] [[body3:%\w+]]
+; CHECK: [[ret2]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[body3]] = OpLabel
+; CHECK-NEXT: [[def2:%\w+]] = OpLogicalAnd
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
+; CHECK: [[break]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def1]] [[break]] [[def1]] [[cont]]
+; CHECK-NEXT: [[phi2:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def2]] [[break]] [[def2]] [[cont]]
+; CHECK: OpLogicalNot {{%\w+}} [[phi1]]
+; CHECK: OpLogicalAnd {{%\w+}} [[phi2]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+OpExecutionMode %func LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %10 %9 None
+OpBranch %3
+%3 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %true %4 %5
+%4 = OpLabel
+OpReturn
+%5 = OpLabel
+%def1 = OpLogicalNot %bool %true
+OpSelectionMerge %7 None
+OpBranchConditional %true %6 %7
+%6 = OpLabel
+OpReturn
+%7 = OpLabel
+%def2 = OpLogicalAnd %bool %true %true
+OpBranchConditional %true %8 %9
+%8 = OpLabel
+OpBranch %10
+%9 = OpLabel
+OpBranchConditional %true %2 %10
+%10 = OpLabel
+OpBranch %11
+%11 = OpLabel
+%use1 = OpLogicalNot %bool %def1
+%use2 = OpLogicalAnd %bool %def2 %true
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, PredicateBreakBlock) {
+  const std::string spirv = R"(
+; IDs are being preserved so we can rely on basic block labels.
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: %13 = OpLabel
+; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
+; CHECK: %8 = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] {{%\w+}} [[undef]] {{%\w+}} [[def]] %13 [[undef]] {{%\w+}}
+; CHECK: OpLogicalAnd {{%\w+}} [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "func"
+OpExecutionMode %1 LocalSize 1 1 1
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpUndef %bool
+%1 = OpFunction %void None %3
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %true %12 %13
+%12 = OpLabel
+OpLoopMerge %14 %15 None
+OpBranch %16
+%16 = OpLabel
+OpReturn
+%15 = OpLabel
+OpBranch %12
+%14 = OpLabel
+OpUnreachable
+%13 = OpLabel
+%17 = OpLogicalNot %bool %true
+OpBranch %8
+%11 = OpLabel
+OpUnreachable
+%9 = OpLabel
+OpBranch %7
+%8 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpLogicalAnd %bool %17 %true
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, SingleReturnInLoop) {
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%9 = OpTypeFunction %float
+%float_1 = OpConstant %float 1
+)";
+
+  const std::string caller =
+      R"(
+; CHECK: OpFunction
+; CHECK: OpFunctionEnd
+%main = OpFunction %void None %7
+%22 = OpLabel
+%30 = OpFunctionCall %float %f_
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string callee =
+      R"(
+; CHECK: OpFunction
+; CHECK: OpLoopMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK: OpReturnValue
+; CHECK-NEXT: OpFunctionEnd
+%f_ = OpFunction %float None %9
+%33 = OpLabel
+OpBranch %34
+%34 = OpLabel
+OpLoopMerge %35 %36 None
+OpBranch %37
+%37 = OpLabel
+OpReturnValue %float_1
+%36 = OpLabel
+OpBranch %34
+%35 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(predefs + caller + callee, true);
+}
+
+TEST_F(MergeReturnPassTest, MergeToMergeBranch) {
+  const std::string text =
+      R"(
+; CHECK: [[new_undef:%\w+]] = OpUndef %uint
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge1:%\w+]]
+; CHECK: OpLoopMerge [[merge2:%\w+]]
+; CHECK: [[merge1]] = OpLabel
+; CHECK-NEXT: OpPhi %uint [[new_undef]] [[merge2]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 100 1 1
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+       %bool = OpTypeBool
+      %false = OpConstantFalse %bool
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpUndef %bool
+          %2 = OpFunction %void None %4
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranchConditional %13 %21 %19
+         %21 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpBranch %18
+         %19 = OpLabel
+         %22 = OpUndef %uint
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+               OpBranch %15
+         %16 = OpLabel
+         %24 = OpCopyObject %uint %22
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(text, true);
+}
+
+TEST_F(MergeReturnPassTest, PhiInSecondMerge) {
+  //  Add and use a phi in the second merge block from the return.
+  const std::string text =
+      R"(
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge_bb:%\w+]] [[continue_bb:%\w+]]
+; CHECK: [[continue_bb]] = OpLabel
+; CHECK-NEXT: [[val:%\w+]] = OpUndef %float
+; CHECK: [[merge_bb]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi %float {{%\w+}} {{%\w+}} [[val]] [[continue_bb]]
+; CHECK-NOT: OpLabel
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[old_merge:%\w+]]
+; CHECK: [[old_merge]] = OpLabel
+; CHECK-NEXT: OpConvertFToS %int [[phi]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %float = OpTypeFloat 32
+       %bool = OpTypeBool
+          %8 = OpUndef %bool
+          %2 = OpFunction %void None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %12 %14 None
+               OpBranchConditional %8 %15 %12
+         %15 = OpLabel
+               OpReturn
+         %14 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+         %16 = OpUndef %float
+               OpBranchConditional %8 %10 %11
+         %11 = OpLabel
+         %17 = OpConvertFToS %int %16
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(text, true);
+}
+
+TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
+  // Make sure that the pass can handle a single block that is both a merge and
+  // a continue.
+  const std::string text =
+      R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+          %2 = OpFunction %void None %4
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpLoopMerge %9 %10 None
+               OpBranch %11
+         %11 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %true %12 %13
+         %12 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpReturn
+         %10 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);
+
+  // Not looking for any particular output.  Other tests do that.
+  // Just want to make sure the check for unreachable blocks does not emit an
+  // error.
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/private_to_local_test.cpp b/test/opt/private_to_local_test.cpp
index 3ec74fa..d154840 100644
--- a/test/opt/private_to_local_test.cpp
+++ b/test/opt/private_to_local_test.cpp
@@ -308,6 +308,117 @@
   SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
 }
 
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterface) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: %priv = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv "priv"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %int %in
+OpStore %priv %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleEntryPoints) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
+; CHECK-NOT: OpEntryPoint GLCompute %foo "bar" %in %priv
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: OpEntryPoint GLCompute %foo "bar" %in
+; CHECK: %priv = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv
+OpEntryPoint GLCompute %foo "bar" %in %priv
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv "priv"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %int %in
+OpStore %priv %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleVariables) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: %priv1 = OpVariable {{%\w+}} Function
+; CHECK: %priv2 = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv1 "priv1"
+OpName %priv2 "priv2"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv1 = OpVariable %ptr_private_int Private
+%priv2 = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %bar1
+%2 = OpFunctionCall %void %bar2
+OpReturn
+OpFunctionEnd
+%bar1 = OpFunction %void None %void_fn
+%3 = OpLabel
+%ld1 = OpLoad %int %in
+OpStore %priv1 %ld1
+OpReturn
+OpFunctionEnd
+%bar2 = OpFunction %void None %void_fn
+%4 = OpLabel
+%ld2 = OpLoad %int %in
+OpStore %priv2 %ld2
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index a53f09d..2ed7b5a 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1620,6 +1620,87 @@
   EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+// Test that id overflow is handled gracefully.
+TEST_F(ScalarReplacementTest, IdBoundOverflow) {
+  const std::string text = R"(
+OpCapability ImageQuery
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main"
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %4194302 DescriptorSet 1073495039
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeStruct %6 %6
+%557056 = OpTypeStruct %7
+%9 = OpTypePointer Function %7
+%18 = OpTypeFunction %7 %9
+%4 = OpFunction %2 Pure|Const %3
+%1836763 = OpLabel
+%4194302 = OpVariable %9 Function
+%10 = OpVariable %9 Function
+OpKill
+%4194301 = OpLabel
+%524296 = OpLoad %7 %4194302
+OpKill
+OpFunctionEnd
+  )";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  std::vector<Message> messages = {
+      {SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."},
+      {SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
+  SetMessageConsumer(GetTestMessageConsumer(messages));
+  auto result =
+      SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
+}
+
+// Test that replacements for OpAccessChain do not go out of bounds.
+// https://github.com/KhronosGroup/SPIRV-Tools/issues/2609.
+TEST_F(ScalarReplacementTest, OutOfBoundOpAccessChain) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %_GLF_color
+               OpExecutionMode %main OriginUpperLeft
+               OpSource ESSL 310
+               OpName %main "main"
+               OpName %a "a"
+               OpName %_GLF_color "_GLF_color"
+               OpDecorate %_GLF_color Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%_ptr_Function__arr_float_uint_1 = OpTypePointer Function %_arr_float_uint_1
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Output_float = OpTypePointer Output %float
+ %_GLF_color = OpVariable %_ptr_Output_float Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %a = OpVariable %_ptr_Function__arr_float_uint_1 Function
+         %21 = OpAccessChain %_ptr_Function_float %a %int_1
+         %22 = OpLoad %float %21
+               OpStore %_GLF_color %22
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  auto result =
+      SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/set_spec_const_default_value_test.cpp b/test/opt/set_spec_const_default_value_test.cpp
index 161674f..5e63862 100644
--- a/test/opt/set_spec_const_default_value_test.cpp
+++ b/test/opt/set_spec_const_default_value_test.cpp
@@ -50,7 +50,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidString, DefaultValuesStringParsingTest,
     ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
         // 0. empty map
@@ -93,7 +93,7 @@
         {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}},
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidString, DefaultValuesStringParsingTest,
     ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
         // 0. missing default value
@@ -148,7 +148,7 @@
       tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidCases, SetSpecConstantDefaultValueInStringFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInStringFormTestCase>{
@@ -445,7 +445,7 @@
         },
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidCases, SetSpecConstantDefaultValueInStringFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInStringFormTestCase>{
@@ -610,7 +610,7 @@
       tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInBitPatternFormTestCase>{
@@ -937,7 +937,7 @@
         },
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInBitPatternFormTestCase>{
diff --git a/test/opt/simplification_test.cpp b/test/opt/simplification_test.cpp
index b7d6f18..4dbcfbe 100644
--- a/test/opt/simplification_test.cpp
+++ b/test/opt/simplification_test.cpp
@@ -202,6 +202,83 @@
   SinglePassRunAndMatch<SimplificationPass>(text, false);
 }
 
+TEST_F(SimplificationTest, CopyObjectWithDecorations1) {
+  // Don't simplify OpCopyObject if the result id has a decoration that the
+  // operand does not.
+  const std::string text = R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpDecorate %3 NonUniformEXT
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%2 = OpFunction %void None %5
+%7 = OpLabel
+%8 = OpUndef %int
+%3 = OpCopyObject %int %8
+%9 = OpIAdd %int %3 %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<SimplificationPass>(text, text, false);
+}
+
+TEST_F(SimplificationTest, CopyObjectWithDecorations2) {
+  // Simplify OpCopyObject if the result id is a subset of the decorations of
+  // the operand.
+  const std::string before = R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpDecorate %3 NonUniformEXT
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%2 = OpFunction %void None %5
+%7 = OpLabel
+%3 = OpUndef %int
+%8 = OpCopyObject %int %3
+%9 = OpIAdd %int %8 %8
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after = R"(OpCapability Shader
+OpCapability ShaderNonUniformEXT
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpDecorate %3 NonUniformEXT
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%2 = OpFunction %void None %5
+%7 = OpLabel
+%3 = OpUndef %int
+%9 = OpIAdd %int %3 %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<SimplificationPass>(before, after, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/split_invalid_unreachable_test.cpp b/test/opt/split_invalid_unreachable_test.cpp
new file mode 100644
index 0000000..868c7b5
--- /dev/null
+++ b/test/opt/split_invalid_unreachable_test.cpp
@@ -0,0 +1,155 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using SplitInvalidUnreachableTest = PassTest<::testing::Test>;
+
+std::string spirv_header = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "shader"
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%7 = OpTypeFunction %void
+)";
+
+std::string function_head = R"(%1 = OpFunction %void None %7
+%8 = OpLabel
+OpBranch %9
+)";
+
+std::string function_tail = "OpFunctionEnd\n";
+
+std::string GetLoopMergeBlock(std::string block_id, std::string merge_id,
+                              std::string continue_id, std::string body_id) {
+  std::string result;
+  result += block_id + " = OpLabel\n";
+  result += "OpLoopMerge " + merge_id + " " + continue_id + " None\n";
+  result += "OpBranch " + body_id + "\n";
+  return result;
+}
+
+std::string GetSelectionMergeBlock(std::string block_id,
+                                   std::string condition_id,
+                                   std::string merge_id, std::string true_id,
+                                   std::string false_id) {
+  std::string result;
+  result += block_id + " = OpLabel\n";
+  result += condition_id + " = OpSLessThan %bool %uint_1 %uint_2\n";
+  result += "OpSelectionMerge " + merge_id + " None\n";
+  result += "OpBranchConditional " + condition_id + " " + true_id + " " +
+            false_id + "\n";
+
+  return result;
+}
+
+std::string GetReturnBlock(std::string block_id) {
+  std::string result;
+  result += block_id + " = OpLabel\n";
+  result += "OpReturn\n";
+  return result;
+}
+
+std::string GetUnreachableBlock(std::string block_id) {
+  std::string result;
+  result += block_id + " = OpLabel\n";
+  result += "OpUnreachable\n";
+  return result;
+}
+
+std::string GetBranchBlock(std::string block_id, std::string target_id) {
+  std::string result;
+  result += block_id + " = OpLabel\n";
+  result += "OpBranch " + target_id + "\n";
+  return result;
+}
+
+TEST_F(SplitInvalidUnreachableTest, NoInvalidBlocks) {
+  std::string input = spirv_header + function_head;
+  input += GetLoopMergeBlock("%9", "%10", "%11", "%12");
+  input += GetSelectionMergeBlock("%12", "%13", "%14", "%15", "%16");
+  input += GetReturnBlock("%15");
+  input += GetReturnBlock("%16");
+  input += GetUnreachableBlock("%10");
+  input += GetBranchBlock("%11", "%9");
+  input += GetUnreachableBlock("%14");
+  input += function_tail;
+
+  SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, input,
+                                                     /* skip_nop = */ false);
+}
+
+TEST_F(SplitInvalidUnreachableTest, SelectionInLoop) {
+  std::string input = spirv_header + function_head;
+  input += GetLoopMergeBlock("%9", "%10", "%11", "%12");
+  input += GetSelectionMergeBlock("%12", "%13", "%11", "%15", "%16");
+  input += GetReturnBlock("%15");
+  input += GetReturnBlock("%16");
+  input += GetUnreachableBlock("%10");
+  input += GetBranchBlock("%11", "%9");
+  input += function_tail;
+
+  std::string expected = spirv_header + function_head;
+  expected += GetLoopMergeBlock("%9", "%10", "%11", "%12");
+  expected += GetSelectionMergeBlock("%12", "%13", "%16", "%14", "%15");
+  expected += GetReturnBlock("%14");
+  expected += GetReturnBlock("%15");
+  expected += GetUnreachableBlock("%10");
+  expected += GetUnreachableBlock("%16");
+  expected += GetBranchBlock("%11", "%9");
+  expected += function_tail;
+
+  SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, expected,
+                                                     /* skip_nop = */ false);
+}
+
+TEST_F(SplitInvalidUnreachableTest, LoopInSelection) {
+  std::string input = spirv_header + function_head;
+  input += GetSelectionMergeBlock("%9", "%10", "%11", "%12", "%13");
+  input += GetLoopMergeBlock("%12", "%14", "%11", "%15");
+  input += GetReturnBlock("%13");
+  input += GetUnreachableBlock("%14");
+  input += GetBranchBlock("%11", "%12");
+  input += GetReturnBlock("%15");
+  input += function_tail;
+
+  std::string expected = spirv_header + function_head;
+  expected += GetSelectionMergeBlock("%9", "%10", "%16", "%12", "%13");
+  expected += GetLoopMergeBlock("%12", "%14", "%11", "%15");
+  expected += GetReturnBlock("%13");
+  expected += GetUnreachableBlock("%14");
+  expected += GetUnreachableBlock("%16");
+  expected += GetBranchBlock("%11", "%12");
+  expected += GetReturnBlock("%15");
+  expected += function_tail;
+
+  SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, expected,
+                                                     /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/strip_atomic_counter_memory_test.cpp b/test/opt/strip_atomic_counter_memory_test.cpp
new file mode 100644
index 0000000..6287a13
--- /dev/null
+++ b/test/opt/strip_atomic_counter_memory_test.cpp
@@ -0,0 +1,406 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+typedef std::tuple<std::string, std::string> StripAtomicCounterMemoryParam;
+
+using MemorySemanticsModified =
+    PassTest<::testing::TestWithParam<StripAtomicCounterMemoryParam>>;
+using NonMemorySemanticsUnmodifiedTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+std::string GetConstDecl(std::string val) {
+  std::string decl;
+  decl += "%uint_" + val + " = OpConstant %uint " + val;
+  return decl;
+}
+
+std::string GetUnchangedString(std::string(generate_inst)(std::string),
+                               std::string val) {
+  std::string decl = GetConstDecl(val);
+  std::string inst = generate_inst(val);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               decl.c_str(),
+         "%1 = OpFunction %void None %8",
+        "%10 = OpLabel",
+               inst.c_str(),
+              "OpReturn",
+              "OpFunctionEnd"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetChangedString(std::string(generate_inst)(std::string),
+                             std::string orig, std::string changed) {
+  std::string orig_decl = GetConstDecl(orig);
+  std::string changed_decl = GetConstDecl(changed);
+  std::string inst = generate_inst(changed);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               orig_decl.c_str() };
+  // clang-format on
+  if (changed != "0") result += changed_decl.c_str();
+  result += "%1 = OpFunction %void None %8";
+  result += "%10 = OpLabel";
+  result += inst.c_str();
+  result += "OpReturn";
+  result += "OpFunctionEnd";
+  return JoinAllInsts(result);
+}
+
+std::tuple<std::string, std::string> GetInputAndExpected(
+    std::string(generate_inst)(std::string),
+    StripAtomicCounterMemoryParam param) {
+  std::string orig = std::get<0>(param);
+  std::string changed = std::get<1>(param);
+  std::string input = GetUnchangedString(generate_inst, orig);
+  std::string expected = orig == changed
+                             ? GetUnchangedString(generate_inst, changed)
+                             : GetChangedString(generate_inst, orig, changed);
+  return std::make_tuple(input, expected);
+}
+
+std::string GetOpControlBarrierInst(std::string val) {
+  return "OpControlBarrier %uint_1 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpControlBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpControlBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryBarrierInst(std::string val) {
+  return "OpMemoryBarrier %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicLoadInst(std::string val) {
+  return "%11 = OpAtomicLoad %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicLoad) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicLoadInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicStoreInst(std::string val) {
+  return "OpAtomicStore %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicStore) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicStoreInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicExchangeInst(std::string val) {
+  return "%11 = OpAtomicExchange %uint %4 %uint_1 %uint_" + val + " %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeInst(std::string val) {
+  return "%11 = OpAtomicCompareExchange %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeWeakInst(std::string val) {
+  return "%11 = OpAtomicCompareExchangeWeak %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchangeWeak) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeWeakInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIIncrementInst(std::string val) {
+  return "%11 = OpAtomicIIncrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIIncrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIIncrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIDecrementInst(std::string val) {
+  return "%11 = OpAtomicIDecrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIDecrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIDecrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIAddInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIAdd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIAddInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicISubInst(std::string val) {
+  return "%11 = OpAtomicISub %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicISub) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicISubInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMinInst(std::string val) {
+  return "%11 = OpAtomicSMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMinInst(std::string val) {
+  return "%11 = OpAtomicUMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMaxInst(std::string val) {
+  return "%11 = OpAtomicSMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMaxInst(std::string val) {
+  return "%11 = OpAtomicUMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicAndInst(std::string val) {
+  return "%11 = OpAtomicAnd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicAnd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicAndInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicOrInst(std::string val) {
+  return "%11 = OpAtomicOr %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicOr) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicOrInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicXorInst(std::string val) {
+  return "%11 = OpAtomicXor %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicXor) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicXorInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagTestAndSetInst(std::string val) {
+  return "%11 = OpAtomicFlagTestAndSet %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagTestAndSet) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagTestAndSetInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagClearInst(std::string val) {
+  return "OpAtomicFlagClear %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagClear) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagClearInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryNamedBarrierInst(std::string val) {
+  return "OpMemoryNamedBarrier %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryNamedBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryNamedBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    StripAtomicCounterMemoryTest, MemorySemanticsModified,
+    ::testing::ValuesIn(std::vector<StripAtomicCounterMemoryParam>({
+       std::make_tuple("1024", "0"),
+       std::make_tuple("5", "5"),
+       std::make_tuple("1288", "264"),
+       std::make_tuple("264", "264")
+    })));
+// clang-format on
+
+std::string GetNoMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpVariable %_ptr_Workgroup_uint Workgroup %uint_" + val;
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, NoMemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "1288");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetNoMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1288";
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, MemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "264");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
index f40ed38..25cf7d8 100644
--- a/test/opt/strip_debug_info_test.cpp
+++ b/test/opt/strip_debug_info_test.cpp
@@ -89,7 +89,7 @@
 
 // Test each possible non-line debug instruction.
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SingleKindDebugInst, StripDebugInfoTest,
     ::testing::ValuesIn(std::vector<const char*>({
         "OpSourceContinued \"I'm a happy shader! Yay! ;)\"",
diff --git a/test/opt/struct_cfg_analysis_test.cpp b/test/opt/struct_cfg_analysis_test.cpp
index 13f9022..7fb3784 100644
--- a/test/opt/struct_cfg_analysis_test.cpp
+++ b/test/opt/struct_cfg_analysis_test.cpp
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/opt/struct_cfg_analysis.h"
+
 #include <string>
 
 #include "gmock/gmock.h"
-#include "source/opt/struct_cfg_analysis.h"
 #include "test/opt/assembly_builder.h"
 #include "test/opt/pass_fixture.h"
 #include "test/opt/pass_utils.h"
@@ -59,18 +60,24 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // BB2 is in the construct.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The merge node is not in the construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, BBInLoop) {
@@ -110,24 +117,32 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // BB2 is in the construct.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The merge node is not in the construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 
   // The continue block is in the construct.
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, SelectionInLoop) {
@@ -172,36 +187,48 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // Selection header is in the loop only.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The loop merge node is not in either construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 
   // The continue block is in the loop only.
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
 
   // BB5 is in the selection fist and the loop.
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 1);
   EXPECT_EQ(analysis.MergeBlock(5), 6);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
 
   // The selection merge is in the loop only.
   EXPECT_EQ(analysis.ContainingConstruct(6), 1);
   EXPECT_EQ(analysis.ContainingLoop(6), 1);
   EXPECT_EQ(analysis.MergeBlock(6), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(6), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, LoopInSelection) {
@@ -246,36 +273,48 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // Loop header is in the selection only.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The selection merge node is not in either construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 
   // The loop merge is in the selection only.
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
 
   // The loop continue target is in the loop.
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 2);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
 
   // BB6 is in the loop.
   EXPECT_EQ(analysis.ContainingConstruct(6), 2);
   EXPECT_EQ(analysis.ContainingLoop(6), 2);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(6), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, SelectionInSelection) {
@@ -318,30 +357,40 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // The inner header is in the outer selection.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The outer merge node is not in either construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 
   // The inner merge is in the outer selection.
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
 
   // BB5 is in the inner selection.
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 0);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, LoopInLoop) {
@@ -388,42 +437,56 @@
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
 
   // The inner loop header is in the outer loop.
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
 
   // The outer merge node is not in either construct.
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
 
   // The inner merge is in the outer loop.
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
 
   // The inner continue target is in the inner loop.
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 2);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
 
   // BB6 is in the loop.
   EXPECT_EQ(analysis.ContainingConstruct(6), 2);
   EXPECT_EQ(analysis.ContainingLoop(6), 2);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(6), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
 
   // The outer continue target is in the outer loop.
   EXPECT_EQ(analysis.ContainingConstruct(7), 1);
   EXPECT_EQ(analysis.ContainingLoop(7), 1);
   EXPECT_EQ(analysis.MergeBlock(7), 3);
   EXPECT_EQ(analysis.LoopMergeBlock(7), 3);
+  EXPECT_EQ(analysis.ContainingSwitch(7), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(7), 0);
 }
 
 TEST_F(StructCFGAnalysisTest, KernelTest) {
@@ -458,9 +521,322 @@
     EXPECT_EQ(analysis.ContainingLoop(i), 0);
     EXPECT_EQ(analysis.MergeBlock(i), 0);
     EXPECT_EQ(analysis.LoopMergeBlock(i), 0);
+    EXPECT_EQ(analysis.ContainingSwitch(i), 0);
+    EXPECT_EQ(analysis.SwitchMergeBlock(i), 0);
   }
 }
 
+TEST_F(StructCFGAnalysisTest, EmptyFunctionTest) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "x" Import
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  // #2451: This segfaulted on empty functions.
+  StructuredCFGAnalysis analysis(context.get());
+}
+
+TEST_F(StructCFGAnalysisTest, BBInSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpSwitch %uint_undef %2 0 %3
+%2 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  StructuredCFGAnalysis analysis(context.get());
+
+  // The header is not in the construct.
+  EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+  EXPECT_EQ(analysis.ContainingLoop(1), 0);
+  EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
+
+  // BB2 is in the construct.
+  EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+  EXPECT_EQ(analysis.ContainingLoop(2), 0);
+  EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
+
+  // The merge node is not in the construct.
+  EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+  EXPECT_EQ(analysis.ContainingLoop(3), 0);
+  EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
+}
+
+TEST_F(StructCFGAnalysisTest, LoopInSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpSwitch %uint_undef %2 1 %3
+%2 = OpLabel
+OpLoopMerge %4 %5 None
+OpBranchConditional %undef_bool %4 %6
+%5 = OpLabel
+OpBranch %2
+%6 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  StructuredCFGAnalysis analysis(context.get());
+
+  // The selection header is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+  EXPECT_EQ(analysis.ContainingLoop(1), 0);
+  EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
+
+  // Loop header is in the selection only.
+  EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+  EXPECT_EQ(analysis.ContainingLoop(2), 0);
+  EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
+
+  // The selection merge node is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+  EXPECT_EQ(analysis.ContainingLoop(3), 0);
+  EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
+
+  // The loop merge is in the selection only.
+  EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+  EXPECT_EQ(analysis.ContainingLoop(4), 0);
+  EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 3);
+
+  // The loop continue target is in the loop.
+  EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+  EXPECT_EQ(analysis.ContainingLoop(5), 2);
+  EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
+
+  // BB6 is in the loop.
+  EXPECT_EQ(analysis.ContainingConstruct(6), 2);
+  EXPECT_EQ(analysis.ContainingLoop(6), 2);
+  EXPECT_EQ(analysis.MergeBlock(6), 4);
+  EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.ContainingSwitch(6), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
+}
+
+TEST_F(StructCFGAnalysisTest, SelectionInSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpSwitch %uint_undef %2 10 %3
+%2 = OpLabel
+OpSelectionMerge %4 None
+OpBranchConditional %undef_bool %4 %5
+%5 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  StructuredCFGAnalysis analysis(context.get());
+
+  // The outer selection header is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+  EXPECT_EQ(analysis.ContainingLoop(1), 0);
+  EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
+
+  // The inner header is in the outer selection.
+  EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+  EXPECT_EQ(analysis.ContainingLoop(2), 0);
+  EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
+
+  // The outer merge node is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+  EXPECT_EQ(analysis.ContainingLoop(3), 0);
+  EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
+
+  // The inner merge is in the outer selection.
+  EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+  EXPECT_EQ(analysis.ContainingLoop(4), 0);
+  EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 3);
+
+  // BB5 is in the inner selection.
+  EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+  EXPECT_EQ(analysis.ContainingLoop(5), 0);
+  EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 1);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 3);
+}
+
+TEST_F(StructCFGAnalysisTest, SwitchInSelection) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpSelectionMerge %4 None
+OpSwitch %uint_undef %4 7 %5
+%5 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  StructuredCFGAnalysis analysis(context.get());
+
+  // The outer selection header is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+  EXPECT_EQ(analysis.ContainingLoop(1), 0);
+  EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(1), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
+
+  // The inner header is in the outer selection.
+  EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+  EXPECT_EQ(analysis.ContainingLoop(2), 0);
+  EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(2), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
+
+  // The outer merge node is not in either construct.
+  EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+  EXPECT_EQ(analysis.ContainingLoop(3), 0);
+  EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(3), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
+
+  // The inner merge is in the outer selection.
+  EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+  EXPECT_EQ(analysis.ContainingLoop(4), 0);
+  EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(4), 0);
+  EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
+
+  // BB5 is in the inner selection.
+  EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+  EXPECT_EQ(analysis.ContainingLoop(5), 0);
+  EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.ContainingSwitch(5), 2);
+  EXPECT_EQ(analysis.SwitchMergeBlock(5), 4);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index 1072c36..267d98c 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -117,10 +117,10 @@
   types.emplace_back(new SampledImage(image2));
 
   // Array
-  types.emplace_back(new Array(f32, 100));
-  types.emplace_back(new Array(f32, 42));
+  types.emplace_back(new Array(f32, Array::LengthInfo{100, {0, 100u}}));
+  types.emplace_back(new Array(f32, Array::LengthInfo{42, {0, 42u}}));
   auto* a42f32 = types.back().get();
-  types.emplace_back(new Array(u64, 24));
+  types.emplace_back(new Array(u64, Array::LengthInfo{24, {0, 24u}}));
 
   // RuntimeArray
   types.emplace_back(new RuntimeArray(v3f32));
@@ -171,7 +171,8 @@
 
 TEST(TypeManager, TypeStrings) {
   const std::string text = R"(
-    OpTypeForwardPointer !20 !2 ; id for %p is 20, Uniform is 2
+    OpDecorate %spec_const_with_id SpecId 99
+    OpTypeForwardPointer %p Uniform
     %void    = OpTypeVoid
     %bool    = OpTypeBool
     %u32     = OpTypeInt 32 0
@@ -201,48 +202,68 @@
     %ps      = OpTypePipeStorage
     %nb      = OpTypeNamedBarrier
     %rtacc   = OpTypeAccelerationStructureNV
+    ; Set up other kinds of OpTypeArray
+    %s64     = OpTypeInt 64 1
+    ; ID 32
+    %spec_const_without_id = OpSpecConstant %s32 44
+    %spec_const_with_id = OpSpecConstant %s32 42 ;; This is ID 1
+    %long_constant = OpConstant %s64 5000000000
+    %spec_const_op = OpSpecConstantOp %s32 IAdd %id4 %id4
+    ; ID 35
+    %arr_spec_const_without_id = OpTypeArray %s32 %spec_const_without_id
+    %arr_spec_const_with_id = OpTypeArray %s32 %spec_const_with_id
+    %arr_long_constant = OpTypeArray %s32 %long_constant
+    %arr_spec_const_op = OpTypeArray %s32 %spec_const_op
   )";
 
   std::vector<std::pair<uint32_t, std::string>> type_id_strs = {
-      {1, "void"},
-      {2, "bool"},
-      {3, "uint32"},
-      // Id 4 is used by the constant.
-      {5, "sint32"},
-      {6, "float64"},
-      {7, "<uint32, 3>"},
-      {8, "<<uint32, 3>, 3>"},
-      {9, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"},
-      {10, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"},
-      {11, "sampler"},
-      {12, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"},
-      {13, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"},
-      {14, "[uint32, id(4)]"},
-      {15, "[float64]"},
-      {16, "{uint32}"},
-      {17, "{float64, sint32, <uint32, 3>}"},
-      {18, "opaque('')"},
-      {19, "opaque('opaque')"},
-      {20, "{uint32}*"},
-      {21, "(uint32, uint32) -> void"},
-      {22, "event"},
-      {23, "device_event"},
-      {24, "reserve_id"},
-      {25, "queue"},
-      {26, "pipe(0)"},
-      {27, "pipe_storage"},
-      {28, "named_barrier"},
-      {29, "accelerationStructureNV"},
+      {3, "void"},
+      {4, "bool"},
+      {5, "uint32"},
+      // Id 6 is used by the constant.
+      {7, "sint32"},
+      {8, "float64"},
+      {9, "<uint32, 3>"},
+      {10, "<<uint32, 3>, 3>"},
+      {11, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"},
+      {12, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"},
+      {13, "sampler"},
+      {14, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"},
+      {15, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"},
+      {16, "[uint32, id(6), words(0,4)]"},
+      {17, "[float64]"},
+      {18, "{uint32}"},
+      {19, "{float64, sint32, <uint32, 3>}"},
+      {20, "opaque('')"},
+      {21, "opaque('opaque')"},
+      {2, "{uint32} 2*"},  // Include storage class number
+      {22, "(uint32, uint32) -> void"},
+      {23, "event"},
+      {24, "device_event"},
+      {25, "reserve_id"},
+      {26, "queue"},
+      {27, "pipe(0)"},
+      {28, "pipe_storage"},
+      {29, "named_barrier"},
+      {30, "accelerationStructureNV"},
+      {31, "sint64"},
+      {35, "[sint32, id(32), words(0,44)]"},
+      {36, "[sint32, id(1), words(1,99,42)]"},
+      {37, "[sint32, id(33), words(0,705032704,1)]"},
+      {38, "[sint32, id(34), words(2,34)]"},
   };
 
   std::unique_ptr<IRContext> context =
       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+  ASSERT_NE(nullptr, context.get());  // It assembled
   TypeManager manager(nullptr, context.get());
 
   EXPECT_EQ(type_id_strs.size(), manager.NumTypes());
 
   for (const auto& p : type_id_strs) {
-    EXPECT_EQ(p.second, manager.GetType(p.first)->str());
+    ASSERT_NE(nullptr, manager.GetType(p.first));
+    EXPECT_EQ(p.second, manager.GetType(p.first)->str())
+        << " id is " << p.first;
     EXPECT_EQ(p.first, manager.GetId(manager.GetType(p.first)));
   }
 }
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
index 7426ed7..fd98806 100644
--- a/test/opt/types_test.cpp
+++ b/test/opt/types_test.cpp
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/opt/types.h"
+
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "gtest/gtest.h"
-#include "source/opt/types.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
@@ -46,8 +47,8 @@
   std::unique_ptr<Type> image_t_;
 };
 
-#define TestMultipleInstancesOfTheSameType(ty, ...)                       \
-  TEST_F(SameTypeTest, MultiSame##ty) {                                   \
+#define TestMultipleInstancesOfTheSameTypeQualified(ty, name, ...)        \
+  TEST_F(SameTypeTest, MultiSame##ty##name) {                             \
     std::vector<std::unique_ptr<Type>> types;                             \
     for (int i = 0; i < 10; ++i) types.emplace_back(new ty(__VA_ARGS__)); \
     for (size_t i = 0; i < types.size(); ++i) {                           \
@@ -61,6 +62,9 @@
       }                                                                   \
     }                                                                     \
   }
+#define TestMultipleInstancesOfTheSameType(ty, ...) \
+  TestMultipleInstancesOfTheSameTypeQualified(ty, Simple, __VA_ARGS__)
+
 TestMultipleInstancesOfTheSameType(Void);
 TestMultipleInstancesOfTheSameType(Bool);
 TestMultipleInstancesOfTheSameType(Integer, 32, true);
@@ -72,7 +76,23 @@
                                    SpvAccessQualifierWriteOnly);
 TestMultipleInstancesOfTheSameType(Sampler);
 TestMultipleInstancesOfTheSameType(SampledImage, image_t_.get());
-TestMultipleInstancesOfTheSameType(Array, u32_t_.get(), 10);
+// There are three classes of arrays, based on the kinds of length information
+// they have.
+// 1. Array length is a constant or spec constant without spec ID, with literals
+// for the constant value.
+TestMultipleInstancesOfTheSameTypeQualified(Array, LenConstant, u32_t_.get(),
+                                            Array::LengthInfo{42,
+                                                              {
+                                                                  0,
+                                                                  9999,
+                                                              }});
+// 2. Array length is a spec constant with a given spec id.
+TestMultipleInstancesOfTheSameTypeQualified(Array, LenSpecId, u32_t_.get(),
+                                            Array::LengthInfo{42, {1, 99}});
+// 3. Array length is an OpSpecConstantOp expression
+TestMultipleInstancesOfTheSameTypeQualified(Array, LenDefiningId, u32_t_.get(),
+                                            Array::LengthInfo{42, {2, 42}});
+
 TestMultipleInstancesOfTheSameType(RuntimeArray, u32_t_.get());
 TestMultipleInstancesOfTheSameType(Struct, std::vector<const Type*>{
                                                u32_t_.get(), f64_t_.get()});
@@ -90,6 +110,7 @@
 TestMultipleInstancesOfTheSameType(NamedBarrier);
 TestMultipleInstancesOfTheSameType(AccelerationStructureNV);
 #undef TestMultipleInstanceOfTheSameType
+#undef TestMultipleInstanceOfTheSameTypeQual
 
 std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
   // Types in this test case are only equal to themselves, nothing else.
@@ -151,10 +172,31 @@
   types.emplace_back(new SampledImage(image2));
 
   // Array
-  types.emplace_back(new Array(f32, 100));
-  types.emplace_back(new Array(f32, 42));
+  // Length is constant with integer bit representation of 42.
+  types.emplace_back(new Array(f32, Array::LengthInfo{99u, {0, 42u}}));
   auto* a42f32 = types.back().get();
-  types.emplace_back(new Array(u64, 24));
+  // Differs from previous in length value only.
+  types.emplace_back(new Array(f32, Array::LengthInfo{99u, {0, 44u}}));
+  // Length is 64-bit constant integer value 42.
+  types.emplace_back(new Array(u64, Array::LengthInfo{100u, {0, 42u, 0u}}));
+  // Differs from previous in length value only.
+  types.emplace_back(new Array(u64, Array::LengthInfo{100u, {0, 44u, 0u}}));
+
+  // Length is spec constant with spec id 18 and default value 44.
+  types.emplace_back(new Array(f32, Array::LengthInfo{99u,
+                                                      {
+                                                          1,
+                                                          18u,
+                                                          44u,
+                                                      }}));
+  // Differs from previous in spec id only.
+  types.emplace_back(new Array(f32, Array::LengthInfo{99u, {1, 19u, 44u}}));
+  // Differs from previous in literal value only.
+  types.emplace_back(new Array(f32, Array::LengthInfo{99u, {1, 19u, 48u}}));
+  // Length is spec constant op with id 42.
+  types.emplace_back(new Array(f32, Array::LengthInfo{42u, {2, 42}}));
+  // Differs from previous in result id only.
+  types.emplace_back(new Array(f32, Array::LengthInfo{43u, {2, 43}}));
 
   // RuntimeArray
   types.emplace_back(new RuntimeArray(v3f32));
@@ -215,8 +257,8 @@
             << types[j]->str() << "'";
       } else {
         EXPECT_FALSE(types[i]->IsSame(types[j].get()))
-            << "expected '" << types[i]->str() << "' is different to '"
-            << types[j]->str() << "'";
+            << "entry (" << i << "," << j << ")  expected '" << types[i]->str()
+            << "' is different to '" << types[j]->str() << "'";
       }
     }
   }
diff --git a/test/opt/unify_const_test.cpp b/test/opt/unify_const_test.cpp
index 37728cc..6ed2173 100644
--- a/test/opt/unify_const_test.cpp
+++ b/test/opt/unify_const_test.cpp
@@ -354,8 +354,14 @@
   Check(expected_builder, test_builder);
 }
 
-INSTANTIATE_TEST_CASE_P(Case, UnifyFrontEndConstantParamTest,
-                        ::testing::ValuesIn(std::vector<UnifyConstantTestCase>({
+INSTANTIATE_TEST_SUITE_P(
+    Case, UnifyFrontEndConstantParamTest,
+    ::
+        testing::
+            ValuesIn(
+                std::
+                    vector<UnifyConstantTestCase>(
+                        {
                             // clang-format off
         // basic tests for scalar constants
         {
diff --git a/test/opt/upgrade_memory_model_test.cpp b/test/opt/upgrade_memory_model_test.cpp
index 9d2d762..b012383 100644
--- a/test/opt/upgrade_memory_model_test.cpp
+++ b/test/opt/upgrade_memory_model_test.cpp
@@ -1713,4 +1713,527 @@
   SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
 }
 
+TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryAddOperands) {
+  const std::string text = R"(
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} None None
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryDuplicateOperand) {
+  const std::string text = R"(
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Nontemporal Nontemporal
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Nontemporal
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryDuplicateOperands) {
+  const std::string text = R"(
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned 4 Aligned 4
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherent) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]] None
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %dst Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherentPreviousArgs) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[scope]] Aligned 4
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %dst Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemorySrcCoherent) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} None MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %src Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemorySrcCoherentPreviousArgs) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned 4 Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 [[scope]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %src Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothCoherent) {
+  const std::string text = R"(
+; CHECK-DAG: [[queue:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK-DAG: [[wg:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[wg]] MakePointerVisibleKHR|NonPrivatePointerKHR [[queue]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %src Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_wg_int = OpTypePointer Workgroup %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothCoherentPreviousArgs) {
+  const std::string text = R"(
+; CHECK-DAG: [[queue:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK-DAG: [[wg:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[queue]] Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 [[wg]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %dst Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_wg_int = OpTypePointer Workgroup %int
+%src = OpVariable %ptr_wg_int Workgroup
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothVolatile) {
+  const std::string text = R"(
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Volatile Volatile
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %src Volatile
+OpDecorate %dst Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothVolatilePreviousArgs) {
+  const std::string text = R"(
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Volatile|Aligned 4 Volatile|Aligned 4
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %src Volatile
+OpDecorate %dst Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherentTwoOperands) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]] None
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %dst Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src None None
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest,
+       SPV14CopyMemoryDstCoherentPreviousArgsTwoOperands) {
+  const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[scope]] Aligned 8
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func" %src %dst
+OpDecorate %dst Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%src = OpVariable %ptr_ssbo_int StorageBuffer
+%dst = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %dst %src Aligned 4 Aligned 8
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicLoad) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32768
+; CHECK: OpAtomicLoad [[int]] {{.*}} {{.*}} [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%relaxed = OpConstant %int 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicLoad %int %ssbo_var %device %relaxed
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicLoadPreviousFlags) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32834
+; CHECK: OpAtomicLoad [[int]] {{.*}} {{.*}} [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%acquire_ssbo = OpConstant %int 66
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicLoad %int %ssbo_var %device %acquire_ssbo
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicStore) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant {{.*}} 32768
+; CHECK: OpAtomicStore {{.*}} {{.*}} [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%device = OpConstant %int 1
+%relaxed = OpConstant %int 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpAtomicStore %ssbo_var %device %relaxed %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicStorePreviousFlags) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant {{.*}} 32836
+; CHECK: OpAtomicStore {{.*}} {{.*}} [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%device = OpConstant %int 1
+%release_ssbo = OpConstant %int 68
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpAtomicStore %ssbo_var %device %release_ssbo %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicCompareExchange) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32768
+; CHECK: OpAtomicCompareExchange [[int]] {{.*}} {{.*}} [[volatile]] [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%device = OpConstant %int 1
+%relaxed = OpConstant %int 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicCompareExchange %int %ssbo_var %device %relaxed %relaxed %int_0 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicCompareExchangePreviousFlags) {
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{.*}} Volatile
+; CHECK: [[volatile_acq_rel:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32840
+; CHECK: [[volatile_acq:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32834
+; CHECK: OpAtomicCompareExchange [[int]] {{.*}} {{.*}} [[volatile_acq_rel]] [[volatile_acq]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ssbo_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%device = OpConstant %int 1
+%acq_ssbo = OpConstant %int 66
+%acq_rel_ssbo = OpConstant %int 72
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicCompareExchange %int %ssbo_var %device %acq_rel_ssbo %acq_ssbo %int_0 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileAtomicLoadMemberDecoration) {
+  const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate {{.*}} {{.*}} Volatile
+; CHECK: [[relaxed:%[a-zA-Z0-9_]+]] = OpConstant {{.*}} 0
+; CHECK: [[volatile:%[a-zA-Z0-9_]+]] = OpConstant [[int:%[a-zA-Z0-9_]+]] 32768
+; CHECK: OpAtomicLoad [[int]] {{.*}} {{.*}} [[relaxed]]
+; CHECK: OpAtomicLoad [[int]] {{.*}} {{.*}} [[volatile]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 1 Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%relaxed = OpConstant %int 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%struct = OpTypeStruct %int %int
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ssbo_var = OpVariable %ptr_ssbo_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep0 = OpAccessChain %ptr_ssbo_int %ssbo_var %int_0
+%ld0 = OpAtomicLoad %int %gep0 %device %relaxed
+%gep1 = OpAccessChain %ptr_ssbo_int %ssbo_var %int_1
+%ld1 = OpAtomicLoad %int %gep1 %device %relaxed
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
 }  // namespace
diff --git a/test/opt/utils_test.cpp b/test/opt/utils_test.cpp
index 9bb82a3..5ce146b 100644
--- a/test/opt/utils_test.cpp
+++ b/test/opt/utils_test.cpp
@@ -72,7 +72,7 @@
       << " expected string: " << GetParam().expected_str;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SubstringReplacement, FindAndReplaceTest,
     ::testing::ValuesIn(std::vector<SubstringReplacementTestCase>({
         // orig string, find substring, replace substring, expected string,
diff --git a/test/opt/value_table_test.cpp b/test/opt/value_table_test.cpp
index ef338ae..0b7530c 100644
--- a/test/opt/value_table_test.cpp
+++ b/test/opt/value_table_test.cpp
@@ -455,6 +455,34 @@
   EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
 }
 
+TEST_F(ValueTableTest, CopyObjectWitDecoration) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource GLSL 430
+               OpDecorate %3 NonUniformEXT
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %2 = OpFunction %4 None %5
+          %8 = OpLabel
+          %9 = OpVariable %7 Function
+         %10 = OpLoad %6 %9
+          %3 = OpCopyObject %6 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+  Instruction* inst2 = context->get_def_use_mgr()->GetDef(3);
+  EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
 // Test that a phi where the operands have the same value assigned that value
 // to the result of the phi.
 TEST_F(ValueTableTest, PhiTest1) {
@@ -495,6 +523,45 @@
   EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi));
 }
 
+TEST_F(ValueTableTest, PhiTest1WithDecoration) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource GLSL 430
+               OpDecorate %3 NonUniformEXT
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %5
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Uniform %6
+          %8 = OpTypeBool
+          %9 = OpConstantTrue %8
+          %10 = OpVariable %7 Uniform
+          %2 = OpFunction %4 None %5
+         %11 = OpLabel
+               OpBranchConditional %9 %12 %13
+         %12 = OpLabel
+         %14 = OpLoad %6 %10
+               OpBranch %15
+         %13 = OpLabel
+         %16 = OpLoad %6 %10
+               OpBranch %15
+         %15 = OpLabel
+         %3 = OpPhi %6 %14 %12 %16 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* inst1 = context->get_def_use_mgr()->GetDef(14);
+  Instruction* inst2 = context->get_def_use_mgr()->GetDef(16);
+  Instruction* phi = context->get_def_use_mgr()->GetDef(3);
+  EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+  EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi));
+}
+
 // When the values for the inputs to a phi do not match, then the phi should
 // have its own value number.
 TEST_F(ValueTableTest, PhiTest2) {
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index b35cdb2..964abdd 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -13,16 +13,23 @@
 # limitations under the License.
 
 add_spvtools_unittest(TARGET reduce
-        SRCS operand_to_constant_reduction_pass_test.cpp
-        operand_to_undef_reduction_pass_test.cpp
-        operand_to_dominating_id_reduction_pass_test.cpp
+        SRCS
+        merge_blocks_test.cpp
+        operand_to_constant_test.cpp
+        operand_to_undef_test.cpp
+        operand_to_dominating_id_test.cpp
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
-        remove_opname_instruction_reduction_pass_test.cpp
-        remove_unreferenced_instruction_reduction_pass_test.cpp
-        structured_loop_to_selection_reduction_pass_test.cpp
+        remove_block_test.cpp
+        remove_function_test.cpp
+        remove_opname_instruction_test.cpp
+        remove_selection_test.cpp
+        remove_unreferenced_instruction_test.cpp
+        structured_loop_to_selection_test.cpp
         validation_during_reduction_test.cpp
+        conditional_branch_to_simple_conditional_branch_test.cpp
+        simple_conditional_branch_to_branch_test.cpp
         LIBS SPIRV-Tools-reduce
         )
 
diff --git a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
new file mode 100644
index 0000000..69ef1f4
--- /dev/null
+++ b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
@@ -0,0 +1,501 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  |         |
+  //  b         b
+  //  |         |
+  //  selection merge
+  //
+  // There should be two opportunities for redirecting the OpBranchConditional
+  // targets: redirecting the true to false, and vice-versa. E.g. false to true:
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b         b
+  //  |         |
+  //  selection merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[0]->TryToApply();
+  // The other opportunity should now be disabled.
+  ASSERT_FALSE(ops[1]->PreconditionHolds());
+
+  CheckValid(kEnv, context.get());
+
+  {
+    std::string after = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %2 "main"
+                 OpExecutionMode %2 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %2 "main"
+            %3 = OpTypeVoid
+            %4 = OpTypeFunction %3
+            %5 = OpTypeInt 32 1
+            %6 = OpTypePointer Function %5
+            %7 = OpTypeBool
+            %8 = OpConstantTrue %7
+            %2 = OpFunction %3 None %4
+            %9 = OpLabel
+                 OpBranch %10
+           %10 = OpLabel
+                 OpSelectionMerge %11 None
+                 OpBranchConditional %8 %12 %12
+           %12 = OpLabel
+                 OpBranch %11
+           %13 = OpLabel
+                 OpBranch %11
+           %11 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+      )";
+    CheckEqual(kEnv, after, context.get());
+  }
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+
+  // Start again, and apply the other op.
+  context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  // The other opportunity should now be disabled.
+  ASSERT_FALSE(ops[0]->PreconditionHolds());
+
+  CheckValid(kEnv, context.get());
+
+  {
+    std::string after2 = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %2 "main"
+                 OpExecutionMode %2 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %2 "main"
+            %3 = OpTypeVoid
+            %4 = OpTypeFunction %3
+            %5 = OpTypeInt 32 1
+            %6 = OpTypePointer Function %5
+            %7 = OpTypeBool
+            %8 = OpConstantTrue %7
+            %2 = OpFunction %3 None %4
+            %9 = OpLabel
+                 OpBranch %10
+           %10 = OpLabel
+                 OpSelectionMerge %11 None
+                 OpBranchConditional %8 %13 %13
+           %12 = OpLabel
+                 OpBranch %11
+           %13 = OpLabel
+                 OpBranch %11
+           %11 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+      )";
+    CheckEqual(kEnv, after2, context.get());
+  }
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, AlreadySimplified) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b         b
+  //  |         |
+  //  selection merge
+  //
+  // There should be no opportunities for redirecting the OpBranchConditional
+  // as it is already simplified.
+  //
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) {
+  // A test with the following structure. The loop has a continue construct that
+  // ends with OpBranchConditional. The OpBranchConditional can be simplified,
+  // but only to point to the loop header, otherwise we have removed the
+  // back-edge. Thus, there should be one opportunity instead of two.
+  //
+  // loop header
+  //   |
+  //   loop continue target and back-edge block
+  //   OpBranchConditional
+  //        |         |
+  // loop merge       (to loop header^)
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest,
+     DontRemoveBackEdgeCombinedHeaderContinue) {
+  // A test with the following structure.
+  //
+  // loop header and continue target and back-edge block
+  //   OpBranchConditional
+  //        |         |
+  // loop merge       (to loop header^)
+  //
+  // The OpBranchConditional-to-header edge must not be removed, so there should
+  // only be one opportunity. It should change both targets to be to the loop
+  // header.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+                              OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) {
+  // A test with the following structure. I.e. a loop with an unreachable
+  // continue construct that ends with OpBranchConditional.
+  //
+  // loop header
+  //   |
+  //   | loop continue target (unreachable)
+  //   |      |
+  //   | back-edge block (unreachable)
+  //   | OpBranchConditional
+  //   |     |         |
+  // loop merge       (to loop header^)
+  //
+  // The branch to the loop header must not be removed, even though the continue
+  // construct is unreachable. So there should only be one opportunity to make
+  // the true and false targets of the OpBranchConditional to point to the loop
+  // header.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp
new file mode 100644
index 0000000..dfb614e
--- /dev/null
+++ b/test/reduce/merge_blocks_test.cpp
@@ -0,0 +1,652 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(MergeBlocksReductionPassTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %13
+         %13 = OpLabel
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(5, ops.size());
+
+  // Try order 3, 0, 2, 4, 1
+
+  ASSERT_TRUE(ops[3]->PreconditionHolds());
+  ops[3]->TryToApply();
+
+  std::string after_op_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %13
+         %13 = OpLabel
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_3, context.get());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+
+  std::string after_op_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_2, context.get());
+
+  ASSERT_TRUE(ops[4]->PreconditionHolds());
+  ops[4]->TryToApply();
+
+  std::string after_op_4 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_4, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, Loops) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+               OpName %29 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %18 = OpConstant %6 10
+         %19 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %45
+         %45 = OpLabel
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpLoad %6 %10
+               OpBranch %46
+         %46 = OpLabel
+         %20 = OpSLessThan %19 %17 %18
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+         %21 = OpLoad %6 %10
+               OpBranch %47
+         %47 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %21
+               OpStore %8 %23
+         %24 = OpLoad %6 %10
+         %25 = OpLoad %6 %8
+         %26 = OpIAdd %6 %25 %24
+               OpStore %8 %26
+               OpBranch %48
+         %48 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+         %27 = OpLoad %6 %10
+         %28 = OpIAdd %6 %27 %9
+               OpStore %10 %28
+               OpBranch %12
+         %14 = OpLabel
+               OpStore %29 %11
+               OpBranch %49
+         %49 = OpLabel
+               OpBranch %30
+         %30 = OpLabel
+               OpLoopMerge %32 %33 None
+               OpBranch %34
+         %34 = OpLabel
+         %35 = OpLoad %6 %29
+         %36 = OpSLessThan %19 %35 %18
+               OpBranch %50
+         %50 = OpLabel
+               OpBranchConditional %36 %31 %32
+         %31 = OpLabel
+         %37 = OpLoad %6 %29
+         %38 = OpLoad %6 %8
+         %39 = OpIAdd %6 %38 %37
+               OpStore %8 %39
+         %40 = OpLoad %6 %29
+         %41 = OpLoad %6 %8
+         %42 = OpIAdd %6 %41 %40
+               OpStore %8 %42
+               OpBranch %33
+         %33 = OpLabel
+         %43 = OpLoad %6 %29
+         %44 = OpIAdd %6 %43 %9
+               OpBranch %51
+         %51 = OpLabel
+               OpStore %29 %44
+               OpBranch %30
+         %32 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(11, ops.size());
+
+  for (auto& ri : ops) {
+    ASSERT_TRUE(ri->PreconditionHolds());
+    ri->TryToApply();
+  }
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+               OpName %29 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %18 = OpConstant %6 10
+         %19 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+         %17 = OpLoad %6 %10
+         %20 = OpSLessThan %19 %17 %18
+               OpLoopMerge %14 %13 None
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+         %21 = OpLoad %6 %10
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %21
+               OpStore %8 %23
+         %24 = OpLoad %6 %10
+         %25 = OpLoad %6 %8
+         %26 = OpIAdd %6 %25 %24
+               OpStore %8 %26
+         %27 = OpLoad %6 %10
+         %28 = OpIAdd %6 %27 %9
+               OpStore %10 %28
+               OpBranch %12
+         %14 = OpLabel
+               OpStore %29 %11
+               OpBranch %30
+         %30 = OpLabel
+         %35 = OpLoad %6 %29
+         %36 = OpSLessThan %19 %35 %18
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %36 %31 %32
+         %31 = OpLabel
+         %37 = OpLoad %6 %29
+         %38 = OpLoad %6 %8
+         %39 = OpIAdd %6 %38 %37
+               OpStore %8 %39
+         %40 = OpLoad %6 %29
+         %41 = OpLoad %6 %8
+         %42 = OpIAdd %6 %41 %40
+               OpStore %8 %42
+         %43 = OpLoad %6 %29
+         %44 = OpIAdd %6 %43 %9
+               OpStore %29 %44
+               OpBranch %30
+         %32 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpPhi %6 %11 %5
+               OpStore %10 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) {
+  // A merge block opportunity stores a block that can be merged with its
+  // predecessor.
+  // Given blocks A -> B -> C:
+  // This test demonstrates how merging B->C can invalidate
+  // the opportunity of merging A->B, and vice-versa. E.g.
+  // B->C are merged: B is now terminated with OpReturn.
+  // A->B can now no longer be merged because A is a loop header, which
+  // cannot be terminated with OpReturn.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B
+               OpBranch %15
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  ASSERT_NE(context.get(), nullptr);
+  auto opportunities =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  // A->B and B->C
+  ASSERT_EQ(opportunities.size(), 2);
+
+  // Test applying opportunities in both orders.
+  if (reverse) {
+    std::reverse(opportunities.begin(), opportunities.end());
+  }
+
+  size_t num_applied = 0;
+  for (auto& ri : opportunities) {
+    if (ri->PreconditionHolds()) {
+      ri->TryToApply();
+      ++num_applied;
+    }
+  }
+
+  // Only 1 opportunity can be applied, as both disable each other.
+  ASSERT_EQ(num_applied, 1);
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A-B (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %15
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // The only difference is the labels.
+  std::string after_reversed = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B-C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, reverse ? after_reversed : after, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturn) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(false);
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturnReverse) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(true);
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/operand_to_constant_reduction_pass_test.cpp b/test/reduce/operand_to_constant_test.cpp
similarity index 92%
rename from test/reduce/operand_to_constant_reduction_pass_test.cpp
rename to test/reduce/operand_to_constant_test.cpp
index 34cc4a1..b2f67ee 100644
--- a/test/reduce/operand_to_constant_reduction_pass_test.cpp
+++ b/test/reduce/operand_to_constant_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -97,8 +99,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToConstReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
   ASSERT_EQ(17, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -146,8 +149,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToConstReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp b/test/reduce/operand_to_dominating_id_test.cpp
similarity index 94%
rename from test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
rename to test/reduce/operand_to_dominating_id_test.cpp
index cc0de65..cd5b2c6 100644
--- a/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
+++ b/test/reduce/operand_to_dominating_id_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
-#include "reduce_test_util.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -53,8 +55,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToDominatingIdReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = OperandToDominatingIdReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(10, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
diff --git a/test/reduce/operand_to_undef_reduction_pass_test.cpp b/test/reduce/operand_to_undef_test.cpp
similarity index 95%
rename from test/reduce/operand_to_undef_reduction_pass_test.cpp
rename to test/reduce/operand_to_undef_test.cpp
index 71bf96c..fa64bd5 100644
--- a/test/reduce/operand_to_undef_reduction_pass_test.cpp
+++ b/test/reduce/operand_to_undef_test.cpp
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/operand_to_undef_reduction_pass.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
 #include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
@@ -163,8 +165,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToUndefReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
 
   ASSERT_EQ(10, ops.size());
 
@@ -216,8 +219,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToUndefReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 19ef749..0c23411 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -12,7 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "test/reduce/reduce_test_util.h"
+
+#include <iostream>
+
+#include "tools/io.h"
 
 namespace spvtools {
 namespace reduce {
@@ -68,5 +72,41 @@
                    const spv_position_t& /*position*/,
                    const char* /*message*/) {}
 
+void CLIMessageConsumer(spv_message_level_t level, const char*,
+                        const spv_position_t& position, const char* message) {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+}
+
+void DumpShader(opt::IRContext* context, const char* filename) {
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  DumpShader(binary, filename);
+}
+
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
+  auto write_file_succeeded =
+      WriteFile(filename, "wb", &binary[0], binary.size());
+  if (!write_file_succeeded) {
+    std::cerr << "Failed to dump shader" << std::endl;
+  }
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h
index 499c774..b9ad12f 100644
--- a/test/reduce/reduce_test_util.h
+++ b/test/reduce/reduce_test_util.h
@@ -16,7 +16,6 @@
 #define TEST_REDUCE_REDUCE_TEST_UTIL_H_
 
 #include "gtest/gtest.h"
-
 #include "source/opt/ir_context.h"
 #include "source/reduce/reduction_opportunity.h"
 #include "spirv-tools/libspirv.h"
@@ -24,23 +23,6 @@
 namespace spvtools {
 namespace reduce {
 
-// A helper class that subclasses a given reduction pass class in order to
-// provide a wrapper for its protected methods.
-template <class ReductionPassT>
-class TestSubclass : public ReductionPassT {
- public:
-  // Creates an instance of the reduction pass subclass with respect to target
-  // environment |env|.
-  explicit TestSubclass(const spv_target_env env) : ReductionPassT(env) {}
-  ~TestSubclass() = default;
-
-  // A wrapper for GetAvailableOpportunities(...)
-  std::vector<std::unique_ptr<ReductionOpportunity>>
-  WrapGetAvailableOpportunities(opt::IRContext* context) const {
-    return ReductionPassT::GetAvailableOpportunities(context);
-  }
-};
-
 // Checks whether the given binaries are bit-wise equal.
 void CheckEqual(spv_target_env env,
                 const std::vector<uint32_t>& expected_binary,
@@ -76,6 +58,17 @@
 void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
                    const spv_position_t& /*position*/, const char* /*message*/);
 
+// Prints reducer messages (for debugging).
+void CLIMessageConsumer(spv_message_level_t level, const char*,
+                        const spv_position_t& position, const char* message);
+
+// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
+// interactive debugging.
+void DumpShader(opt::IRContext* context, const char* filename);
+
+// Dumps |binary| to file |filename|. Useful for interactive debugging.
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename);
+
 }  // namespace reduce
 }  // namespace spvtools
 
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 88fc5e4..8787733 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
-
-#include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/reducer.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -217,9 +217,10 @@
       [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
         return ping_pong_interesting.IsInteresting(binary);
       });
-  reducer.AddReductionPass(MakeUnique<OperandToConstReductionPass>(env));
   reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+      MakeUnique<OperandToConstReductionOpportunityFinder>());
+  reducer.AddReductionPass(
+      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -228,8 +229,13 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  reducer_options.set_fail_on_validation_error(true);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   CheckEqual(env, expected, binary_out);
 }
@@ -254,7 +260,7 @@
          %10 = OpLabel
           %3 = OpVariable %8 Function
           %4 = OpLoad %7 %3
-               OpStore %3 %7
+               OpStore %3 %9
                OpReturn
                OpFunctionEnd
   )";
@@ -279,7 +285,7 @@
 
   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
   Reducer reducer(env);
-  // Make ping-pong interesting very quickly, as there are not much
+  // Make ping-pong interesting very quickly, as there are not many
   // opportunities.
   PingPongInteresting ping_pong_interesting(1);
   reducer.SetMessageConsumer(NopDiagnostic);
@@ -288,9 +294,9 @@
         return ping_pong_interesting.IsInteresting(binary);
       });
   reducer.AddReductionPass(
-      MakeUnique<RemoveOpNameInstructionReductionPass>(env));
+      MakeUnique<RemoveOpNameInstructionReductionOpportunityFinder>());
   reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -299,12 +305,17 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  reducer_options.set_fail_on_validation_error(true);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   CheckEqual(env, expected, binary_out);
 }
 
 }  // namespace
 }  // namespace reduce
-}  // namespace spvtools
\ No newline at end of file
+}  // namespace spvtools
diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp
new file mode 100644
index 0000000..f31cc9d
--- /dev/null
+++ b/test/reduce/remove_block_test.cpp
@@ -0,0 +1,358 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveBlockReductionPassTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %13 = OpLabel ; unreachable
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel ; unreachable
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) {
+  // Loop with unreachable merge and continue target. There should be no
+  // opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %16 %15 None
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+         %15 = OpLabel
+               OpBranch %13
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, OneBlock) {
+  // Function with just one block. There should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) {
+  // A function with two unreachable blocks A -> B. A defines ID %9 and B uses
+  // %9. There are no references to A, but removing A would be invalid because
+  // of B's use of %9, so there should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel          ; A
+          %9 = OpUndef %4
+               OpBranch %10
+         %10 = OpLabel          ; B
+         %11 = OpIAdd %4 %6 %9  ; uses %9 from A, so A cannot be removed
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) {
+  // Similar to the above test.
+
+  // A function with two unreachable blocks A -> B. Both blocks create and use
+  // IDs, but the uses are contained within each block, so A should be removed.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel                     ; A
+          %9 = OpUndef %4                  ; define %9
+         %10 = OpIAdd %4 %6 %9             ; use %9
+               OpBranch %11
+         %11 = OpLabel                     ; B
+         %12 = OpUndef %4                  ; define %12
+         %13 = OpIAdd %4 %6 %12            ; use %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block A is removed.
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+         %11 = OpLabel
+         %12 = OpUndef %4
+         %13 = OpIAdd %4 %6 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  // Find opportunities again. There are no reference to B. B should now be
+  // removed.
+
+  ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block B is removed.
+  std::string after_op_0_again = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0_again, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_function_test.cpp b/test/reduce/remove_function_test.cpp
new file mode 100644
index 0000000..576b603
--- /dev/null
+++ b/test/reduce/remove_function_test.cpp
@@ -0,0 +1,295 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+// Helper to count the number of functions in the module.
+// Remove if there turns out to be a more direct way to do this.
+uint32_t count_functions(opt::IRContext* context) {
+  uint32_t result = 0;
+  for (auto& function : *context->module()) {
+    (void)(function);
+    ++result;
+  }
+  return result;
+}
+
+TEST(RemoveFunctionTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(2, count_functions(context.get()));
+
+  std::string after_first = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_first, context.get());
+
+  ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after_second = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_second, context.get());
+}
+
+TEST(RemoveFunctionTest, NothingToRemove) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, TwoRemovableFunctions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_EQ(2, count_functions(context.get()));
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+TEST(RemoveFunctionTest, NoRemovalsDueToOpName) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "foo("
+               OpName %8 "bar("
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) {
+  // The non-entry point function is not removable because it is referenced by a
+  // linkage decoration. Thus no function can be removed.
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpName %1 "main"
+               OpDecorate %2 LinkageAttributes "ExportedFunc" Export
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %1 = OpFunction %4 None %5
+          %6 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %2 = OpFunction %4 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp b/test/reduce/remove_opname_instruction_test.cpp
similarity index 83%
rename from test/reduce/remove_opname_instruction_reduction_pass_test.cpp
rename to test/reduce/remove_opname_instruction_test.cpp
index 38a2d7f..9d40cfc 100644
--- a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
+++ b/test/reduce/remove_opname_instruction_test.cpp
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
 
 #include "source/opt/build_module.h"
 #include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -42,8 +43,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, source, kReduceAssembleOption);
-  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(0, ops.size());
 }
 
@@ -76,8 +77,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -126,14 +127,14 @@
   const std::string expected = prologue + epilogue;
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
-  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
 
   {
     // Check the right number of opportunities is detected
     const auto consumer = nullptr;
     const auto context =
         BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                         .GetAvailableOpportunities(context.get());
     ASSERT_EQ(5, ops.size());
   }
 
@@ -142,7 +143,11 @@
     std::vector<uint32_t> binary;
     SpirvTools t(env);
     ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary = pass.TryApplyReduction(binary);
+    auto reduced_binary =
+        ReductionPass(env,
+                      spvtools::MakeUnique<
+                          RemoveOpNameInstructionReductionOpportunityFinder>())
+            .TryApplyReduction(binary);
     CheckEqual(env, expected, reduced_binary);
   }
 }
@@ -190,14 +195,14 @@
   const std::string expected = prologue + epilogue;
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
-  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
 
   {
     // Check the right number of opportunities is detected
     const auto consumer = nullptr;
     const auto context =
         BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                         .GetAvailableOpportunities(context.get());
     ASSERT_EQ(6, ops.size());
   }
 
@@ -206,7 +211,11 @@
     std::vector<uint32_t> binary;
     SpirvTools t(env);
     ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary = pass.TryApplyReduction(binary);
+    auto reduced_binary =
+        ReductionPass(env,
+                      spvtools::MakeUnique<
+                          RemoveOpNameInstructionReductionOpportunityFinder>())
+            .TryApplyReduction(binary);
     CheckEqual(env, expected, reduced_binary);
   }
 }
diff --git a/test/reduce/remove_selection_test.cpp b/test/reduce/remove_selection_test.cpp
new file mode 100644
index 0000000..f8acd5d
--- /dev/null
+++ b/test/reduce/remove_selection_test.cpp
@@ -0,0 +1,557 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // header
+  // ||
+  // block
+  // |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranchConditional %8 %11 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // header
+  // ||
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %10 %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocksOneMerge) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // |  |
+  // | block
+  // |  |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %10 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocks) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // | |
+  // b b
+  // | |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseMergeUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // ||
+  // block
+  // |  |
+  // | block
+  // |  |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpBranchConditional %8 %10 %12
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // loop header
+  //    |
+  //    |
+  //   s.header
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   s.merge |    |
+  //    |     /   loop continue target (unreachable)
+  // loop merge
+  //
+  //
+  // which becomes:
+  //
+  // loop header
+  //    |
+  //    |
+  //   block
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   block   |    |
+  //    |     /   loop continue target (unreachable)
+  // loop merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(env, context.get());
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // loop header
+  //    |
+  //    |
+  //   s.header
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   s.merge |    |
+  //    |     loop continue target
+  // loop merge
+  //
+  //
+  // which becomes:
+  //
+  // loop header
+  //    |
+  //    |
+  //   block
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   block   |    |
+  //    |     loop continue target
+  // loop merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %12
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(env, context.get());
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %12
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp b/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
deleted file mode 100644
index a002fa3..0000000
--- a/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "reduce_test_util.h"
-
-#include "source/opt/build_module.h"
-#include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-namespace {
-
-TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %12 "c"
-               OpName %14 "d"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 10
-         %11 = OpConstant %6 20
-         %13 = OpConstant %6 30
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %10 = OpVariable %7 Function
-         %12 = OpVariable %7 Function
-         %14 = OpVariable %7 Function
-  )";
-
-  const std::string epilogue = R"(
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const std::string expected = prologue + R"(
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass =
-      TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
-  ASSERT_EQ(4, ops.size());
-  ASSERT_TRUE(ops[0]->PreconditionHolds());
-  ops[0]->TryToApply();
-  ASSERT_TRUE(ops[1]->PreconditionHolds());
-  ops[1]->TryToApply();
-
-  CheckEqual(env, expected, context.get());
-}
-
-TEST(RemoveUnreferencedInstructionReductionPassTest, ApplyReduction) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %12 "c"
-               OpName %14 "d"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 10
-         %11 = OpConstant %6 20
-         %13 = OpConstant %6 30
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %10 = OpVariable %7 Function
-         %12 = OpVariable %7 Function
-         %14 = OpVariable %7 Function
-  )";
-
-  const std::string epilogue = R"(
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-
-  std::vector<uint32_t> binary;
-  SpirvTools t(env);
-  ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-
-  auto pass = TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
-
-  {
-    // Attempt 1 should remove everything removable.
-    const std::string expected_reduced = prologue + R"(
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 2 should fail as pass with granularity 4 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  {
-    // Attempt 3 should remove first two removable statements.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 4 should remove last two removable statements.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 5 should fail as pass with granularity 2 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  {
-    // Attempt 6 should remove first removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 7 should remove second removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 8 should remove third removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 9 should remove fourth removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 10 should fail as pass with granularity 1 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  ASSERT_TRUE(pass.ReachedMinimumGranularity());
-}
-
-}  // namespace
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/test/reduce/remove_unreferenced_instruction_test.cpp b/test/reduce/remove_unreferenced_instruction_test.cpp
new file mode 100644
index 0000000..0babf78
--- /dev/null
+++ b/test/reduce/remove_unreferenced_instruction_test.cpp
@@ -0,0 +1,101 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/util/make_unique.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
+  const std::string prologue = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpName %12 "c"
+               OpName %14 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 20
+         %13 = OpConstant %6 30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+  )";
+
+  const std::string epilogue = R"(
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string original = prologue + R"(
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %13
+         %15 = OpLoad %6 %8
+               OpStore %14 %15
+  )" + epilogue;
+
+  const std::string expected_after_2 = prologue + R"(
+               OpStore %12 %13
+         %15 = OpLoad %6 %8
+               OpStore %14 %15
+  )" + epilogue;
+
+  const std::string expected_after_4 = prologue + R"(
+         %15 = OpLoad %6 %8
+  )" + epilogue;
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, original, kReduceAssembleOption);
+  const auto ops = RemoveUnreferencedInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(4, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  CheckEqual(env, expected_after_2, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+  ASSERT_TRUE(ops[3]->PreconditionHolds());
+  ops[3]->TryToApply();
+
+  CheckEqual(env, expected_after_4, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/simple_conditional_branch_to_branch_test.cpp b/test/reduce/simple_conditional_branch_to_branch_test.cpp
new file mode 100644
index 0000000..d55e691
--- /dev/null
+++ b/test/reduce/simple_conditional_branch_to_branch_test.cpp
@@ -0,0 +1,486 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+
+TEST(SimpleConditionalBranchToBranchTest, Diamond) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b        b
+  //  |        |
+  //  selection merge
+  //
+  // The conditional branch cannot be simplified because selection headers
+  // cannot end with OpBranch.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) {
+  // A test with the following structure.
+  //
+  // OpBranchConditional
+  //  ||
+  //  b  b
+  //  | /
+  //  b
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, ConditionalBranchesButNotSimple) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  |        |
+  //  b        OpBranchConditional
+  //  |        |   |
+  //  |        b   |
+  //  |        |   |
+  //  selection merge
+  //
+  // None of the conditional branches can be simplified; the first is not simple
+  // AND part of a selection header; the second is just not simple (where
+  // "simple" means it only has one target).
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) {
+  // A test with the following structure. The loop has a continue construct that
+  // ends with OpBranchConditional. The OpBranchConditional can be simplified.
+  //
+  // loop header
+  //   |
+  //   loop continue target and back-edge block
+  //   OpBranchConditional
+  //                  ||
+  // loop merge       (to loop header^)
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest,
+     DontRemoveBackEdgeCombinedHeaderContinue) {
+  // A test with the following structure.
+  //
+  // loop header and continue target and back-edge block
+  //   OpBranchConditional
+  //                  ||
+  // loop merge       (to loop header^)
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+          OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) {
+  // A test with the following structure. I.e. a loop with an unreachable
+  // continue construct that ends with OpBranchConditional.
+  //
+  // loop header
+  //   |
+  //   | loop continue target (unreachable)
+  //   |      |
+  //   | back-edge block (unreachable)
+  //   | OpBranchConditional
+  //   |               ||
+  // loop merge       (to loop header^)
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp
similarity index 90%
rename from test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
rename to test/reduce/structured_loop_to_selection_test.cpp
index 8388cb2..95b5f4f 100644
--- a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
+++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
-#include "reduce_test_util.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -62,8 +64,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -208,8 +210,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(4, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -677,8 +679,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(0, ops.size());
 }
 
@@ -755,8 +757,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // Initially there are two opportunities.
   ASSERT_EQ(2, ops.size());
@@ -878,8 +880,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -953,8 +955,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1021,8 +1023,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1221,8 +1223,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1688,8 +1690,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2005,8 +2007,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2153,8 +2155,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2310,8 +2312,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2418,8 +2420,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2508,8 +2510,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2552,8 +2554,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2592,8 +2594,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2667,8 +2669,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2730,8 +2732,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2787,8 +2789,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2871,8 +2873,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2967,8 +2969,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3086,8 +3088,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3206,8 +3208,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
 
   // We cannot transform the inner loop due to its header jumping straight to
   // the outer loop merge (the inner loop's merge does not post-dominate its
@@ -3251,7 +3253,8 @@
   CheckEqual(env, after_op_0, context.get());
 
   // Now look again for more opportunities.
-  ops = pass.WrapGetAvailableOpportunities(context.get());
+  ops = StructuredLoopToSelectionReductionOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
 
   // What was the inner loop should now be transformable, as the jump to the
   // outer loop's merge has been redirected.
@@ -3418,8 +3421,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3435,6 +3438,191 @@
   // CheckEqual(env, expected, context.get());
 }
 
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShaderWithOpDecorate) {
+  // A shader containing a function that contains a loop and some definitions
+  // that are "used" in OpDecorate instructions (outside the function). These
+  // "uses" were causing segfaults because we try to calculate their dominance
+  // information, which doesn't make sense.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %14 "buf0"
+               OpMemberName %14 0 "a"
+               OpName %16 ""
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpMemberDecorate %14 0 RelaxedPrecision
+               OpMemberDecorate %14 0 Offset 0
+               OpDecorate %14 Block
+               OpDecorate %16 DescriptorSet 0
+               OpDecorate %16 Binding 0
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpConstant %6 1
+         %11 = OpConstantComposite %7 %10 %10 %10 %10
+         %14 = OpTypeStruct %6
+         %15 = OpTypePointer Uniform %14
+         %16 = OpVariable %15 Uniform
+         %17 = OpTypeInt 32 1
+         %18 = OpConstant %17 0
+         %19 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 2
+         %29 = OpTypeBool
+         %31 = OpTypeInt 32 0
+         %32 = OpConstant %31 0
+         %33 = OpTypePointer Output %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %9 %11
+         %20 = OpAccessChain %19 %16 %18
+         %21 = OpLoad %6 %20
+               OpBranch %22
+         %22 = OpLabel
+         %40 = OpPhi %6 %21 %5 %39 %23
+         %30 = OpFOrdLessThan %29 %40 %28
+               OpLoopMerge %24 %23 None
+               OpBranchConditional %30 %23 %24
+         %23 = OpLabel
+         %34 = OpAccessChain %33 %9 %32
+         %35 = OpLoad %6 %34
+         %36 = OpFAdd %6 %35 %10
+               OpStore %34 %36
+         %39 = OpFAdd %6 %40 %10
+               OpBranch %22
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %14 "buf0"
+               OpMemberName %14 0 "a"
+               OpName %16 ""
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpMemberDecorate %14 0 RelaxedPrecision
+               OpMemberDecorate %14 0 Offset 0
+               OpDecorate %14 Block
+               OpDecorate %16 DescriptorSet 0
+               OpDecorate %16 Binding 0
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpConstant %6 1
+         %11 = OpConstantComposite %7 %10 %10 %10 %10
+         %14 = OpTypeStruct %6
+         %15 = OpTypePointer Uniform %14
+         %16 = OpVariable %15 Uniform
+         %17 = OpTypeInt 32 1
+         %18 = OpConstant %17 0
+         %19 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 2
+         %29 = OpTypeBool
+         %31 = OpTypeInt 32 0
+         %32 = OpConstant %31 0
+         %33 = OpTypePointer Output %6
+         %41 = OpUndef %6                          ; Added
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %9 %11
+         %20 = OpAccessChain %19 %16 %18
+         %21 = OpLoad %6 %20
+               OpBranch %22
+         %22 = OpLabel
+         %40 = OpPhi %6 %21 %5 %41 %23             ; Changed
+         %30 = OpFOrdLessThan %29 %40 %28
+               OpSelectionMerge %24 None           ; Changed
+               OpBranchConditional %30 %24 %24
+         %23 = OpLabel
+         %34 = OpAccessChain %33 %9 %32
+         %35 = OpLoad %6 %34
+         %36 = OpFAdd %6 %35 %10
+               OpStore %34 %36
+         %39 = OpFAdd %6 %41 %10                   ; Changed
+               OpBranch %22
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     LoopWithCombinedHeaderAndContinue) {
+  // A shader containing a loop where the header is also the continue target.
+  // For now, we don't simplify such loops.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+         %30 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                       ; loop header and continue target
+               OpLoopMerge %12 %10 None
+               OpBranchConditional %30 %10 %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp
index bb7d14e..4051bac 100644
--- a/test/reduce/validation_during_reduction_test.cpp
+++ b/test/reduce/validation_during_reduction_test.cpp
@@ -12,38 +12,39 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
-
 #include "source/reduce/reducer.h"
-#include "source/reduce/reduction_pass.h"
+
+#include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
 namespace {
 
-// A dumb reduction pass that removes global values regardless of whether they
-// are referenced. This is very likely to make the resulting module invalid.  We
-// use this to test the reducer's behavior in the scenario where a bad reduction
-// pass leads to an invalid module.
-class BlindlyRemoveGlobalValuesPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit BlindlyRemoveGlobalValuesPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
 
-  ~BlindlyRemoveGlobalValuesPass() override = default;
+// A dumb reduction opportunity finder that finds opportunities to remove global
+// values regardless of whether they are referenced. This is very likely to make
+// the resulting module invalid.  We use this to test the reducer's behavior in
+// the scenario where a bad reduction pass leads to an invalid module.
+class BlindlyRemoveGlobalValuesReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  BlindlyRemoveGlobalValuesReductionOpportunityFinder() = default;
+
+  ~BlindlyRemoveGlobalValuesReductionOpportunityFinder() override = default;
 
   // The name of this pass.
-  std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; };
+  std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; }
 
- protected:
-  // Adds opportunities to remove all global values.  Assuming they are all
+  // Finds opportunities to remove all global values.  Assuming they are all
   // referenced (directly or indirectly) from elsewhere in the module, each such
   // opportunity will make the module invalid.
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final {
+      IRContext* context) const final {
     std::vector<std::unique_ptr<ReductionOpportunity>> result;
     for (auto& inst : context->module()->types_values()) {
       if (inst.HasResultId()) {
@@ -55,9 +56,70 @@
   }
 };
 
+// A dumb reduction opportunity that exists at the start of every function whose
+// first instruction is an OpVariable instruction. When applied, the OpVariable
+// instruction is duplicated (with a fresh result id). This allows each
+// reduction step to increase the number of variables to check if the validator
+// limits are enforced.
+class OpVariableDuplicatorReductionOpportunity : public ReductionOpportunity {
+ public:
+  OpVariableDuplicatorReductionOpportunity(Function* function)
+      : function_(function) {}
+
+  bool PreconditionHolds() override {
+    Instruction* first_instruction = &*function_->begin()[0].begin();
+    return first_instruction->opcode() == SpvOpVariable;
+  }
+
+ protected:
+  void Apply() override {
+    // Duplicate the first OpVariable instruction.
+
+    Instruction* first_instruction = &*function_->begin()[0].begin();
+    assert(first_instruction->opcode() == SpvOpVariable &&
+           "Expected first instruction to be OpVariable");
+    IRContext* context = first_instruction->context();
+    Instruction* cloned_instruction = first_instruction->Clone(context);
+    cloned_instruction->SetResultId(context->TakeNextId());
+    cloned_instruction->InsertBefore(first_instruction);
+  }
+
+ private:
+  Function* function_;
+};
+
+// A reduction opportunity finder that finds
+// OpVariableDuplicatorReductionOpportunity.
+class OpVariableDuplicatorReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OpVariableDuplicatorReductionOpportunityFinder() = default;
+
+  ~OpVariableDuplicatorReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final {
+    return "LocalVariableAdderReductionOpportunityFinder";
+  }
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      IRContext* context) const final {
+    std::vector<std::unique_ptr<ReductionOpportunity>> result;
+    for (auto& function : *context->module()) {
+      Instruction* first_instruction = &*function.begin()[0].begin();
+      if (first_instruction->opcode() == SpvOpVariable) {
+        result.push_back(
+            MakeUnique<OpVariableDuplicatorReductionOpportunity>(&function));
+      }
+    }
+    return result;
+  }
+};
+
 TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
   // A module whose global values are all referenced, so that any application of
-  // MakeModuleInvalidPass will make the module invalid.
+  // MakeModuleInvalidPass will make the module invalid. Check that the reducer
+  // makes no progress, as every step will be invalid and treated as
+  // uninteresting.
   std::string original = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -153,7 +215,8 @@
   reducer.SetInterestingnessFunction(
       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
 
-  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -162,8 +225,14 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  // Don't fail on a validation error; just treat it as uninteresting.
+  reducer_options.set_fail_on_validation_error(false);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   // The reducer should have no impact.
   CheckEqual(env, original, binary_out);
@@ -357,7 +426,8 @@
   reducer.SetInterestingnessFunction(
       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
 
-  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -366,11 +436,149 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  // Don't fail on a validation error; just treat it as uninteresting.
+  reducer_options.set_fail_on_validation_error(false);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
+
   CheckEqual(env, expected, binary_out);
 }
 
+// Sets up a Reducer for use in the CheckValidationOptions test; avoids
+// repetition.
+void SetupReducerForCheckValidationOptions(Reducer* reducer) {
+  reducer->SetMessageConsumer(NopDiagnostic);
+
+  // Say that every module is interesting.
+  reducer->SetInterestingnessFunction(
+      [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+  // Each "reduction" step will duplicate the first OpVariable instruction in
+  // the function.
+  reducer->AddReductionPass(
+      MakeUnique<OpVariableDuplicatorReductionOpportunityFinder>());
+}
+
+TEST(ValidationDuringReductionTest, CheckValidationOptions) {
+  // A module that only validates when the "skip-block-layout" validator option
+  // is used. Also, the entry point's first instruction creates a local
+  // variable; this instruction will be duplicated on each reduction step.
+  std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "Main" %3
+               OpSource HLSL 600
+               OpDecorate %3 BuiltIn Position
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 99
+               OpDecorate %5 ArrayStride 16
+               OpMemberDecorate %6 0 Offset 0
+               OpMemberDecorate %6 1 Offset 32
+               OpMemberDecorate %6 1 MatrixStride 16
+               OpMemberDecorate %6 1 ColMajor
+               OpMemberDecorate %6 2 Offset 96
+               OpMemberDecorate %6 3 Offset 100
+               OpMemberDecorate %6 4 Offset 112
+               OpMemberDecorate %6 4 MatrixStride 16
+               OpMemberDecorate %6 4 ColMajor
+               OpMemberDecorate %6 5 Offset 176
+               OpDecorate %6 Block
+          %7 = OpTypeFloat 32
+          %8 = OpTypeVector %7 4
+          %9 = OpTypeMatrix %8 4
+         %10 = OpTypeVector %7 2
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeInt 32 0
+         %13 = OpConstant %12 2
+         %14 = OpConstant %11 1
+         %15 = OpConstant %11 5
+          %5 = OpTypeArray %8 %13
+          %6 = OpTypeStruct %5 %9 %12 %10 %9 %7
+         %16 = OpTypePointer Uniform %6
+         %17 = OpTypePointer Output %8
+         %18 = OpTypeVoid
+         %19 = OpTypeFunction %18
+         %20 = OpTypePointer Uniform %7
+          %4 = OpVariable %16 Uniform
+          %3 = OpVariable %17 Output
+         %21 = OpTypePointer Function %11
+          %2 = OpFunction %18 None %19
+         %22 = OpLabel
+         %23 = OpVariable %21 Function
+         %24 = OpAccessChain %20 %4 %15
+         %25 = OpLoad %7 %24
+         %26 = OpCompositeConstruct %8 %25 %25 %25 %25
+               OpStore %3 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+
+  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  spvtools::ValidatorOptions validator_options;
+
+  reducer_options.set_step_limit(3);
+  reducer_options.set_fail_on_validation_error(true);
+
+  // Reduction should fail because the initial state is invalid without the
+  // "skip-block-layout" validator option. Note that the interestingness test
+  // always returns true.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kInitialStateInvalid);
+  }
+
+  // Try again with validator option.
+  validator_options.SetSkipBlockLayout(true);
+
+  // Reduction should hit step limit; module is seen as valid, interestingness
+  // test always succeeds, and the finder yields infinite opportunities.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kReachedStepLimit);
+  }
+
+  // Now set a limit on the number of local variables.
+  validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables,
+                                      2);
+
+  // Reduction should now fail due to reaching an invalid state; after one step,
+  // a local variable is added and the module becomes "invalid" given the
+  // validator limits.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kStateInvalid);
+  }
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/scripts/test_compact_ids.py b/test/scripts/test_compact_ids.py
index b9b5b1b..6ca6e67 100644
--- a/test/scripts/test_compact_ids.py
+++ b/test/scripts/test_compact_ids.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Tests correctness of opt pass tools/opt --compact-ids."""
 
-from __future__ import print_function
-
 import os.path
 import sys
 import tempfile
diff --git a/test/stats/CMakeLists.txt b/test/stats/CMakeLists.txt
deleted file mode 100644
index 393cb24..0000000
--- a/test/stats/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# 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.
-
-set(VAL_TEST_COMMON_SRCS
-  ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h
-  ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
-)
-
-add_spvtools_unittest(TARGET stats
-	SRCS stats_aggregate_test.cpp
-       stats_analyzer_test.cpp
-       ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/stats/spirv_stats.cpp
-	     ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/stats/stats_analyzer.cpp
-       ${VAL_TEST_COMMON_SRCS}
-  LIBS ${SPIRV_TOOLS}
-)
diff --git a/test/stats/stats_aggregate_test.cpp b/test/stats/stats_aggregate_test.cpp
deleted file mode 100644
index 0745285..0000000
--- a/test/stats/stats_aggregate_test.cpp
+++ /dev/null
@@ -1,438 +0,0 @@
-// 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.
-
-// Tests for unique type declaration rules validator.
-
-#include <string>
-#include <unordered_map>
-
-#include "test/test_fixture.h"
-#include "test/unit_spirv.h"
-#include "tools/stats/spirv_stats.h"
-
-namespace spvtools {
-namespace stats {
-namespace {
-
-using spvtest::ScopedContext;
-
-void DiagnosticsMessageHandler(spv_message_level_t level, const char*,
-                               const spv_position_t& position,
-                               const char* message) {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: " << position.index << ": " << message << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cout << "warning: " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cout << "info: " << position.index << ": " << message << std::endl;
-      break;
-    default:
-      break;
-  }
-}
-
-// Calls AggregateStats for binary compiled from |code|.
-void CompileAndAggregateStats(const std::string& code, SpirvStats* stats,
-                              spv_target_env env = SPV_ENV_UNIVERSAL_1_1) {
-  spvtools::Context ctx(env);
-  ctx.SetMessageConsumer(DiagnosticsMessageHandler);
-  spv_binary binary;
-  ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(ctx.CContext(), code.c_str(),
-                                         code.size(), &binary, nullptr));
-
-  ASSERT_EQ(SPV_SUCCESS, AggregateStats(ctx.CContext(), binary->code,
-                                        binary->wordCount, nullptr, stats));
-  spvBinaryDestroy(binary);
-}
-
-TEST(AggregateStats, CapabilityHistogram) {
-  const std::string code1 = R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-)";
-
-  const std::string code2 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-)";
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(4u, stats.capability_hist.size());
-  EXPECT_EQ(0u, stats.capability_hist.count(SpvCapabilityShader));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityAddresses));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityKernel));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityGenericPointer));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityLinkage));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(5u, stats.capability_hist.size());
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityShader));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityAddresses));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityKernel));
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityGenericPointer));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityLinkage));
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(5u, stats.capability_hist.size());
-  EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityShader));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityAddresses));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityKernel));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityGenericPointer));
-  EXPECT_EQ(3u, stats.capability_hist.at(SpvCapabilityLinkage));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(5u, stats.capability_hist.size());
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityShader));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityAddresses));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityKernel));
-  EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityGenericPointer));
-  EXPECT_EQ(4u, stats.capability_hist.at(SpvCapabilityLinkage));
-}
-
-TEST(AggregateStats, ExtensionHistogram) {
-  const std::string code1 = R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpExtension "SPV_KHR_16bit_storage"
-OpMemoryModel Physical32 OpenCL
-)";
-
-  const std::string code2 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpExtension "SPV_NV_viewport_array2"
-OpExtension "greatest_extension_ever"
-OpMemoryModel Logical GLSL450
-)";
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(1u, stats.extension_hist.size());
-  EXPECT_EQ(0u, stats.extension_hist.count("SPV_NV_viewport_array2"));
-  EXPECT_EQ(1u, stats.extension_hist.at("SPV_KHR_16bit_storage"));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(3u, stats.extension_hist.size());
-  EXPECT_EQ(1u, stats.extension_hist.at("SPV_NV_viewport_array2"));
-  EXPECT_EQ(1u, stats.extension_hist.at("SPV_KHR_16bit_storage"));
-  EXPECT_EQ(1u, stats.extension_hist.at("greatest_extension_ever"));
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(3u, stats.extension_hist.size());
-  EXPECT_EQ(1u, stats.extension_hist.at("SPV_NV_viewport_array2"));
-  EXPECT_EQ(2u, stats.extension_hist.at("SPV_KHR_16bit_storage"));
-  EXPECT_EQ(1u, stats.extension_hist.at("greatest_extension_ever"));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(3u, stats.extension_hist.size());
-  EXPECT_EQ(2u, stats.extension_hist.at("SPV_NV_viewport_array2"));
-  EXPECT_EQ(2u, stats.extension_hist.at("SPV_KHR_16bit_storage"));
-  EXPECT_EQ(2u, stats.extension_hist.at("greatest_extension_ever"));
-}
-
-TEST(AggregateStats, VersionHistogram) {
-  const std::string code1 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-)";
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(1u, stats.version_hist.size());
-  EXPECT_EQ(1u, stats.version_hist.at(0x00010100));
-
-  CompileAndAggregateStats(code1, &stats, SPV_ENV_UNIVERSAL_1_0);
-  EXPECT_EQ(2u, stats.version_hist.size());
-  EXPECT_EQ(1u, stats.version_hist.at(0x00010100));
-  EXPECT_EQ(1u, stats.version_hist.at(0x00010000));
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(2u, stats.version_hist.size());
-  EXPECT_EQ(2u, stats.version_hist.at(0x00010100));
-  EXPECT_EQ(1u, stats.version_hist.at(0x00010000));
-
-  CompileAndAggregateStats(code1, &stats, SPV_ENV_UNIVERSAL_1_0);
-  EXPECT_EQ(2u, stats.version_hist.size());
-  EXPECT_EQ(2u, stats.version_hist.at(0x00010100));
-  EXPECT_EQ(2u, stats.version_hist.at(0x00010000));
-}
-
-TEST(AggregateStats, GeneratorHistogram) {
-  const std::string code1 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-)";
-
-  const uint32_t kGeneratorKhronosAssembler = SPV_GENERATOR_KHRONOS_ASSEMBLER
-                                              << 16;
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(1u, stats.generator_hist.size());
-  EXPECT_EQ(1u, stats.generator_hist.at(kGeneratorKhronosAssembler));
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(1u, stats.generator_hist.size());
-  EXPECT_EQ(2u, stats.generator_hist.at(kGeneratorKhronosAssembler));
-}
-
-TEST(AggregateStats, OpcodeHistogram) {
-  const std::string code1 = R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability Int64
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-%u64 = OpTypeInt 64 0
-%u32 = OpTypeInt 32 0
-%f32 = OpTypeFloat 32
-)";
-
-  const std::string code2 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpExtension "SPV_NV_viewport_array2"
-OpMemoryModel Logical GLSL450
-)";
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(4u, stats.opcode_hist.size());
-  EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpCapability));
-  EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpMemoryModel));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeInt));
-  EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpTypeFloat));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(5u, stats.opcode_hist.size());
-  EXPECT_EQ(6u, stats.opcode_hist.at(SpvOpCapability));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpMemoryModel));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeInt));
-  EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpTypeFloat));
-  EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpExtension));
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(5u, stats.opcode_hist.size());
-  EXPECT_EQ(10u, stats.opcode_hist.at(SpvOpCapability));
-  EXPECT_EQ(3u, stats.opcode_hist.at(SpvOpMemoryModel));
-  EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpTypeInt));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeFloat));
-  EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpExtension));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(5u, stats.opcode_hist.size());
-  EXPECT_EQ(12u, stats.opcode_hist.at(SpvOpCapability));
-  EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpMemoryModel));
-  EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpTypeInt));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeFloat));
-  EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpExtension));
-}
-
-TEST(AggregateStats, OpcodeMarkovHistogram) {
-  const std::string code1 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpExtension "SPV_NV_viewport_array2"
-OpMemoryModel Logical GLSL450
-)";
-
-  const std::string code2 = R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability Int64
-OpCapability Linkage
-OpMemoryModel Physical32 OpenCL
-%u64 = OpTypeInt 64 0
-%u32 = OpTypeInt 32 0
-%f32 = OpTypeFloat 32
-)";
-
-  SpirvStats stats;
-  stats.opcode_markov_hist.resize(2);
-
-  CompileAndAggregateStats(code1, &stats);
-  ASSERT_EQ(2u, stats.opcode_markov_hist.size());
-  EXPECT_EQ(2u, stats.opcode_markov_hist[0].size());
-  EXPECT_EQ(2u, stats.opcode_markov_hist[0].at(SpvOpCapability).size());
-  EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpExtension).size());
-  EXPECT_EQ(
-      1u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpCapability));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpExtension));
-  EXPECT_EQ(
-      1u, stats.opcode_markov_hist[0].at(SpvOpExtension).at(SpvOpMemoryModel));
-
-  EXPECT_EQ(1u, stats.opcode_markov_hist[1].size());
-  EXPECT_EQ(2u, stats.opcode_markov_hist[1].at(SpvOpCapability).size());
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpExtension));
-  EXPECT_EQ(
-      1u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpMemoryModel));
-
-  CompileAndAggregateStats(code2, &stats);
-  ASSERT_EQ(2u, stats.opcode_markov_hist.size());
-  EXPECT_EQ(4u, stats.opcode_markov_hist[0].size());
-  EXPECT_EQ(3u, stats.opcode_markov_hist[0].at(SpvOpCapability).size());
-  EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpExtension).size());
-  EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpMemoryModel).size());
-  EXPECT_EQ(2u, stats.opcode_markov_hist[0].at(SpvOpTypeInt).size());
-  EXPECT_EQ(
-      4u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpCapability));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpExtension));
-  EXPECT_EQ(
-      1u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpMemoryModel));
-  EXPECT_EQ(
-      1u, stats.opcode_markov_hist[0].at(SpvOpExtension).at(SpvOpMemoryModel));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[0].at(SpvOpMemoryModel).at(SpvOpTypeInt));
-  EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpTypeInt).at(SpvOpTypeInt));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[0].at(SpvOpTypeInt).at(SpvOpTypeFloat));
-
-  EXPECT_EQ(3u, stats.opcode_markov_hist[1].size());
-  EXPECT_EQ(4u, stats.opcode_markov_hist[1].at(SpvOpCapability).size());
-  EXPECT_EQ(1u, stats.opcode_markov_hist[1].at(SpvOpMemoryModel).size());
-  EXPECT_EQ(1u, stats.opcode_markov_hist[1].at(SpvOpTypeInt).size());
-  EXPECT_EQ(
-      2u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpCapability));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpExtension));
-  EXPECT_EQ(
-      2u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpMemoryModel));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpTypeInt));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[1].at(SpvOpMemoryModel).at(SpvOpTypeInt));
-  EXPECT_EQ(1u,
-            stats.opcode_markov_hist[1].at(SpvOpTypeInt).at(SpvOpTypeFloat));
-}
-
-TEST(AggregateStats, ConstantLiteralsHistogram) {
-  const std::string code1 = R"(
-OpCapability Addresses
-OpCapability Kernel
-OpCapability GenericPointer
-OpCapability Linkage
-OpCapability Float64
-OpCapability Int16
-OpCapability Int64
-OpMemoryModel Physical32 OpenCL
-%u16 = OpTypeInt 16 0
-%u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
-%f32 = OpTypeFloat 32
-%f64 = OpTypeFloat 64
-%1 = OpConstant %f32 0.1
-%2 = OpConstant %f32 -2
-%3 = OpConstant %f64 -2
-%4 = OpConstant %u16 16
-%5 = OpConstant %u16 2
-%6 = OpConstant %u32 32
-%7 = OpConstant %u64 64
-)";
-
-  const std::string code2 = R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability Int16
-OpCapability Int64
-OpMemoryModel Logical GLSL450
-%f32 = OpTypeFloat 32
-%u16 = OpTypeInt 16 0
-%s16 = OpTypeInt 16 1
-%u32 = OpTypeInt 32 0
-%s32 = OpTypeInt 32 1
-%u64 = OpTypeInt 64 0
-%s64 = OpTypeInt 64 1
-%1 = OpConstant %f32 0.1
-%2 = OpConstant %f32 -2
-%3 = OpConstant %u16 1
-%4 = OpConstant %u16 16
-%5 = OpConstant %u16 2
-%6 = OpConstant %s16 -16
-%7 = OpConstant %u32 32
-%8 = OpConstant %s32 2
-%9 = OpConstant %s32 -32
-%10 = OpConstant %u64 64
-%11 = OpConstant %s64 -64
-)";
-
-  SpirvStats stats;
-
-  CompileAndAggregateStats(code1, &stats);
-  EXPECT_EQ(2u, stats.f32_constant_hist.size());
-  EXPECT_EQ(1u, stats.f64_constant_hist.size());
-  EXPECT_EQ(1u, stats.f32_constant_hist.at(0.1f));
-  EXPECT_EQ(1u, stats.f32_constant_hist.at(-2.f));
-  EXPECT_EQ(1u, stats.f64_constant_hist.at(-2));
-
-  EXPECT_EQ(2u, stats.u16_constant_hist.size());
-  EXPECT_EQ(0u, stats.s16_constant_hist.size());
-  EXPECT_EQ(1u, stats.u32_constant_hist.size());
-  EXPECT_EQ(0u, stats.s32_constant_hist.size());
-  EXPECT_EQ(1u, stats.u64_constant_hist.size());
-  EXPECT_EQ(0u, stats.s64_constant_hist.size());
-  EXPECT_EQ(1u, stats.u16_constant_hist.at(16));
-  EXPECT_EQ(1u, stats.u16_constant_hist.at(2));
-  EXPECT_EQ(1u, stats.u32_constant_hist.at(32));
-  EXPECT_EQ(1u, stats.u64_constant_hist.at(64));
-
-  CompileAndAggregateStats(code2, &stats);
-  EXPECT_EQ(2u, stats.f32_constant_hist.size());
-  EXPECT_EQ(1u, stats.f64_constant_hist.size());
-  EXPECT_EQ(2u, stats.f32_constant_hist.at(0.1f));
-  EXPECT_EQ(2u, stats.f32_constant_hist.at(-2.f));
-  EXPECT_EQ(1u, stats.f64_constant_hist.at(-2));
-
-  EXPECT_EQ(3u, stats.u16_constant_hist.size());
-  EXPECT_EQ(1u, stats.s16_constant_hist.size());
-  EXPECT_EQ(1u, stats.u32_constant_hist.size());
-  EXPECT_EQ(2u, stats.s32_constant_hist.size());
-  EXPECT_EQ(1u, stats.u64_constant_hist.size());
-  EXPECT_EQ(1u, stats.s64_constant_hist.size());
-  EXPECT_EQ(2u, stats.u16_constant_hist.at(16));
-  EXPECT_EQ(2u, stats.u16_constant_hist.at(2));
-  EXPECT_EQ(1u, stats.u16_constant_hist.at(1));
-  EXPECT_EQ(1u, stats.s16_constant_hist.at(-16));
-  EXPECT_EQ(2u, stats.u32_constant_hist.at(32));
-  EXPECT_EQ(1u, stats.s32_constant_hist.at(2));
-  EXPECT_EQ(1u, stats.s32_constant_hist.at(-32));
-  EXPECT_EQ(2u, stats.u64_constant_hist.at(64));
-  EXPECT_EQ(1u, stats.s64_constant_hist.at(-64));
-}
-
-}  // namespace
-}  // namespace stats
-}  // namespace spvtools
diff --git a/test/stats/stats_analyzer_test.cpp b/test/stats/stats_analyzer_test.cpp
deleted file mode 100644
index 3764c5b..0000000
--- a/test/stats/stats_analyzer_test.cpp
+++ /dev/null
@@ -1,174 +0,0 @@
-// 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.
-
-// Tests for unique type declaration rules validator.
-
-#include <sstream>
-#include <string>
-
-#include "source/latest_version_spirv_header.h"
-#include "test/test_fixture.h"
-#include "tools/stats/stats_analyzer.h"
-
-namespace spvtools {
-namespace stats {
-namespace {
-
-// Fills |stats| with some synthetic header stats, as if aggregated from 100
-// modules (100 used for simpler percentage evaluation).
-void FillDefaultStats(SpirvStats* stats) {
-  *stats = SpirvStats();
-  stats->version_hist[0x00010000] = 40;
-  stats->version_hist[0x00010100] = 60;
-  stats->generator_hist[0x00000000] = 64;
-  stats->generator_hist[0x00010000] = 1;
-  stats->generator_hist[0x00020000] = 2;
-  stats->generator_hist[0x00030000] = 3;
-  stats->generator_hist[0x00040000] = 4;
-  stats->generator_hist[0x00050000] = 5;
-  stats->generator_hist[0x00060000] = 6;
-  stats->generator_hist[0x00070000] = 7;
-  stats->generator_hist[0x00080000] = 8;
-
-  int num_version_entries = 0;
-  for (const auto& pair : stats->version_hist) {
-    num_version_entries += pair.second;
-  }
-
-  int num_generator_entries = 0;
-  for (const auto& pair : stats->generator_hist) {
-    num_generator_entries += pair.second;
-  }
-
-  EXPECT_EQ(num_version_entries, num_generator_entries);
-}
-
-TEST(StatsAnalyzer, Version) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteVersion(ss);
-  const std::string output = ss.str();
-  const std::string expected_output = "Version 1.1 60%\nVersion 1.0 40%\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-TEST(StatsAnalyzer, Generator) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteGenerator(ss);
-  const std::string output = ss.str();
-  const std::string expected_output =
-      "Khronos 64%\nKhronos Glslang Reference Front End 8%\n"
-      "Khronos SPIR-V Tools Assembler 7%\nKhronos LLVM/SPIR-V Translator 6%"
-      "\nARM 5%\nNVIDIA 4%\nCodeplay 3%\nValve 2%\nLunarG 1%\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-TEST(StatsAnalyzer, Capability) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  stats.capability_hist[SpvCapabilityShader] = 25;
-  stats.capability_hist[SpvCapabilityKernel] = 75;
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteCapability(ss);
-  const std::string output = ss.str();
-  const std::string expected_output = "Kernel 75%\nShader 25%\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-TEST(StatsAnalyzer, Extension) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  stats.extension_hist["greatest_extension_ever"] = 1;
-  stats.extension_hist["worst_extension_ever"] = 10;
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteExtension(ss);
-  const std::string output = ss.str();
-  const std::string expected_output =
-      "worst_extension_ever 10%\ngreatest_extension_ever 1%\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-TEST(StatsAnalyzer, Opcode) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  stats.opcode_hist[SpvOpCapability] = 20;
-  stats.opcode_hist[SpvOpConstant] = 80;
-  stats.opcode_hist[SpvOpDecorate] = 100;
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteOpcode(ss);
-  const std::string output = ss.str();
-  const std::string expected_output =
-      "Total unique opcodes used: 3\nDecorate 50%\n"
-      "Constant 40%\nCapability 10%\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-TEST(StatsAnalyzer, OpcodeMarkov) {
-  SpirvStats stats;
-  FillDefaultStats(&stats);
-
-  stats.opcode_hist[SpvOpFMul] = 400;
-  stats.opcode_hist[SpvOpFAdd] = 200;
-  stats.opcode_hist[SpvOpFSub] = 400;
-
-  stats.opcode_markov_hist.resize(1);
-  auto& hist = stats.opcode_markov_hist[0];
-  hist[SpvOpFMul][SpvOpFAdd] = 100;
-  hist[SpvOpFMul][SpvOpFSub] = 300;
-  hist[SpvOpFAdd][SpvOpFMul] = 100;
-  hist[SpvOpFAdd][SpvOpFAdd] = 100;
-
-  StatsAnalyzer analyzer(stats);
-
-  std::stringstream ss;
-  analyzer.WriteOpcodeMarkov(ss);
-  const std::string output = ss.str();
-  const std::string expected_output =
-      "FMul -> FSub 75% (base rate 40%, pair occurrences 300)\n"
-      "FMul -> FAdd 25% (base rate 20%, pair occurrences 100)\n"
-      "FAdd -> FAdd 50% (base rate 20%, pair occurrences 100)\n"
-      "FAdd -> FMul 50% (base rate 40%, pair occurrences 100)\n";
-
-  EXPECT_EQ(expected_output, output);
-}
-
-}  // namespace
-}  // namespace stats
-}  // namespace spvtools
diff --git a/test/target_env_test.cpp b/test/target_env_test.cpp
index f962464..9f80e83 100644
--- a/test/target_env_test.cpp
+++ b/test/target_env_test.cpp
@@ -45,8 +45,8 @@
   ASSERT_THAT(spirv_version, AnyOf(0x10000, 0x10100, 0x10200, 0x10300));
 }
 
-INSTANTIATE_TEST_CASE_P(AllTargetEnvs, TargetEnvTest,
-                        ValuesIn(spvtest::AllTargetEnvironments()));
+INSTANTIATE_TEST_SUITE_P(AllTargetEnvs, TargetEnvTest,
+                         ValuesIn(spvtest::AllTargetEnvironments()));
 
 TEST(GetContextTest, InvalidTargetEnvProducesNull) {
   // Use a value beyond the last valid enum value.
@@ -70,7 +70,7 @@
   EXPECT_THAT(env, Eq(GetParam().env));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TargetParsing, TargetParseTest,
     ValuesIn(std::vector<ParseCase>{
         {"spv1.0", true, SPV_ENV_UNIVERSAL_1_0},
diff --git a/test/text_literal_test.cpp b/test/text_literal_test.cpp
index 7028089..28e3500 100644
--- a/test/text_literal_test.cpp
+++ b/test/text_literal_test.cpp
@@ -106,7 +106,7 @@
   EXPECT_EQ(std::get<1>(GetParam()), l.str);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextLiteral, GoodStringTest,
     ::testing::ValuesIn(std::vector<std::pair<const char*, const char*>>{
         {R"("-")", "-"},
@@ -121,7 +121,7 @@
         {"\"\xE4\xBA\xB2\"", "\xE4\xBA\xB2"},
         {"\"\\\xE4\xBA\xB2\"", "\xE4\xBA\xB2"},
         {"\"this \\\" and this \\\\ and \\\xE4\xBA\xB2\"",
-         "this \" and this \\ and \xE4\xBA\xB2"}}), );
+         "this \" and this \\ and \xE4\xBA\xB2"}}));
 
 TEST(TextLiteral, StringTooLong) {
   spv_literal_t l;
@@ -230,7 +230,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecimalIntegers, IntegerTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Check max value and overflow value for 1-bit numbers.
@@ -277,7 +277,7 @@
         Make_Ok__Unsigned(64, "18446744073709551615", {0xffffffff, 0xffffffff}),
         Make_Ok__Signed(64, "-9223372036854775808", {0x00000000, 0x80000000}),
 
-    }),);
+    }));
 // clang-format on
 
 using IntegerLeadingMinusTest =
@@ -290,7 +290,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecimalAndHexIntegers, IntegerLeadingMinusTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Unsigned numbers never allow a leading minus sign.
@@ -303,10 +303,10 @@
         Make_Bad_Unsigned(64, "-0"),
         Make_Bad_Unsigned(64, "-0x0"),
         Make_Bad_Unsigned(64, "-0x1"),
-    }),);
+    }));
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     HexIntegers, IntegerTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Check 0x and 0X prefices.
@@ -370,7 +370,7 @@
         Make_Ok__Signed(64, "0x8000000000000000", {0x00000000, 0x80000000}),
         Make_Ok__Unsigned(64, "0x7fffffffffffffff", {0xffffffff, 0x7fffffff}),
         Make_Ok__Unsigned(64, "0x8000000000000000", {0x00000000, 0x80000000}),
-    }),);
+    }));
 // clang-format on
 
 TEST(OverflowIntegerParse, Decimal) {
diff --git a/test/text_to_binary.annotation_test.cpp b/test/text_to_binary.annotation_test.cpp
index 7aec905..69a4861 100644
--- a/test/text_to_binary.annotation_test.cpp
+++ b/test/text_to_binary.annotation_test.cpp
@@ -60,8 +60,31 @@
       Eq(input.str()));
 }
 
+// Like above, but parameters to the decoration are IDs.
+using OpDecorateSimpleIdTest =
+    spvtest::TextToBinaryTestBase<::testing::TestWithParam<
+        std::tuple<spv_target_env, EnumCase<SpvDecoration>>>>;
+
+TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) {
+  // This string should assemble, but should not validate.
+  std::stringstream input;
+  input << "OpDecorateId %1 " << std::get<1>(GetParam()).name();
+  for (auto operand : std::get<1>(GetParam()).operands())
+    input << " %" << operand;
+  input << std::endl;
+  EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
+              Eq(MakeInstruction(SpvOpDecorateId,
+                                 {1, uint32_t(std::get<1>(GetParam()).value())},
+                                 std::get<1>(GetParam()).operands())));
+  // Also check disassembly.
+  EXPECT_THAT(
+      EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
+                                  std::get<0>(GetParam())),
+      Eq(input.str()));
+}
+
 #define CASE(NAME) SpvDecoration##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimple, OpDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvDecoration>>{
@@ -106,12 +129,27 @@
                 {CASE(NoContraction), {}},
                 {CASE(InputAttachmentIndex), {102}},
                 {CASE(Alignment), {16}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
-                        Combine(Values(SPV_ENV_UNIVERSAL_1_1),
-                                Values(EnumCase<SpvDecoration>{
-                                    CASE(MaxByteOffset), {128}})), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_1),
+                                 Values(EnumCase<SpvDecoration>{
+                                     CASE(MaxByteOffset), {128}})));
+
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV14, OpDecorateSimpleTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_4),
+                                 ValuesIn(std::vector<EnumCase<SpvDecoration>>{
+                                     {CASE(Uniform), {}},
+                                 })));
+
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleIdV14,
+                         OpDecorateSimpleIdTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_4),
+                                 ValuesIn(std::vector<EnumCase<SpvDecoration>>{
+                                     // In 1.4, UniformId decoration takes a
+                                     // scope Id.
+                                     {CASE(UniformId), {1}},
+                                 })));
 #undef CASE
 
 TEST_F(OpDecorateSimpleTest, WrongDecoration) {
@@ -164,7 +202,7 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvBuiltIn##NAME, #NAME, SpvDecorationBuiltIn, "BuiltIn" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(Position),
                             CASE(PointSize),
@@ -209,7 +247,7 @@
                             CASE(SubgroupLocalInvocationId),
                             CASE(VertexIndex),
                             CASE(InstanceIndex),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -222,7 +260,7 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvFunctionParameterAttribute##NAME, #NAME, SpvDecorationFuncParamAttr, "FuncParamAttr" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(Zext),
                             CASE(Sext),
@@ -232,7 +270,7 @@
                             CASE(NoCapture),
                             CASE(NoWrite),
                             CASE(NoReadWrite),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -245,13 +283,13 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvFPRoundingMode##NAME, #NAME, SpvDecorationFPRoundingMode, "FPRoundingMode" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(RTE),
                             CASE(RTZ),
                             CASE(RTP),
                             CASE(RTN),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -268,7 +306,7 @@
 // clang-format off
 #define CASE(ENUM,NAME) \
   { SpvFPFastMathMode##ENUM, #NAME, SpvDecorationFPFastMathMode, "FPFastMathMode" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(MaskNone, None),
                             CASE(NotNaNMask, NotNaN),
@@ -276,7 +314,7 @@
                             CASE(NSZMask, NSZ),
                             CASE(AllowRecipMask, AllowRecip),
                             CASE(FastMask, Fast),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -329,13 +367,13 @@
 
 // clang-format off
 #define CASE(ENUM) SpvLinkageType##ENUM, #ENUM
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest,
                         ::testing::ValuesIn(std::vector<DecorateLinkageCase>{
                             { CASE(Import), "a" },
                             { CASE(Export), "foo" },
                             { CASE(Import), "some kind of long name with spaces etc." },
                             // TODO(dneto): utf-8, escaping, quoting cases.
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -423,7 +461,7 @@
 }
 
 #define CASE(NAME) SpvDecoration##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimple, OpMemberDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvDecoration>>{
@@ -468,12 +506,12 @@
                 {CASE(NoContraction), {}},
                 {CASE(InputAttachmentIndex), {102}},
                 {CASE(Alignment), {16}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimpleV11, OpMemberDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
-            Values(EnumCase<SpvDecoration>{CASE(MaxByteOffset), {128}})), );
+            Values(EnumCase<SpvDecoration>{CASE(MaxByteOffset), {128}})));
 #undef CASE
 
 TEST_F(OpMemberDecorateSimpleTest, WrongDecoration) {
diff --git a/test/text_to_binary.composite_test.cpp b/test/text_to_binary.composite_test.cpp
new file mode 100644
index 0000000..6ae1cd3
--- /dev/null
+++ b/test/text_to_binary.composite_test.cpp
@@ -0,0 +1,49 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Assembler tests for instructions in the "Group Instrucions" section of the
+// SPIR-V spec.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+namespace spvtools {
+namespace {
+
+using spvtest::Concatenate;
+
+using CompositeRoundTripTest = RoundTripTest;
+
+TEST_F(CompositeRoundTripTest, Good) {
+  std::string spirv = "%2 = OpCopyLogical %1 %3\n";
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(CompositeRoundTripTest, V13Bad) {
+  std::string spirv = "%2 = OpCopyLogical %1 %3\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpCopyLogical'"));
+}
+
+}  // namespace
+}  // namespace spvtools
diff --git a/test/text_to_binary.constant_test.cpp b/test/text_to_binary.constant_test.cpp
index 1a24b52..6624d41 100644
--- a/test/text_to_binary.constant_test.cpp
+++ b/test/text_to_binary.constant_test.cpp
@@ -47,7 +47,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvSamplerAddressingMode##NAME, #NAME }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinarySamplerAddressingMode, SamplerAddressingModeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvSamplerAddressingMode>>{
         CASE(None),
@@ -55,7 +55,7 @@
         CASE(Clamp),
         CASE(Repeat),
         CASE(RepeatMirrored),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -79,12 +79,12 @@
 
 // clang-format off
 #define CASE(NAME) { SpvSamplerFilterMode##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinarySamplerFilterMode, SamplerFilterModeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvSamplerFilterMode>>{
         CASE(Nearest),
         CASE(Linear),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -114,7 +114,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantValid, OpConstantValidTest,
     ::testing::ValuesIn(std::vector<ConstantTestCase>{
       // Check 16 bits
@@ -232,7 +232,7 @@
       {"OpTypeInt 64 1", "0x7fffffff",
         Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}),
          MakeInstruction(SpvOpConstant, {1, 2, 0x7fffffffu, 0})})},
-    }),);
+    }));
 // clang-format on
 
 // A test case for checking OpConstant with invalid literals with a leading
@@ -255,7 +255,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantInvalidLeadingMinus, OpConstantInvalidLeadingMinusTest,
     ::testing::ValuesIn(std::vector<InvalidLeadingMinusCase>{
       {"OpTypeInt 16 0", "-0"},
@@ -267,7 +267,7 @@
       {"OpTypeInt 64 0", "-0"},
       {"OpTypeInt 64 0", "-0x0"},
       {"OpTypeInt 64 0", "-1"},
-    }),);
+    }));
 // clang-format on
 
 // A test case for invalid floating point literals.
@@ -293,7 +293,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryInvalidFloatConstant, OpConstantInvalidFloatConstant,
     ::testing::ValuesIn(std::vector<InvalidFloatConstantCase>{
         {16, "abc"},
@@ -323,7 +323,7 @@
         {64, "++1"},
         {32, "1e400"}, // Overflow is an error for 64-bit floats.
         {32, "-1e400"},
-    }),);
+    }));
 // clang-format on
 
 using OpConstantInvalidTypeTest =
@@ -339,7 +339,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantInvalidValidType, OpConstantInvalidTypeTest,
     ::testing::ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -364,7 +364,7 @@
         // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 using OpSpecConstantValidTest =
@@ -381,7 +381,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantValid, OpSpecConstantValidTest,
     ::testing::ValuesIn(std::vector<ConstantTestCase>{
       // Check 16 bits
@@ -433,7 +433,7 @@
       {"OpTypeInt 64 1", "-42",
         Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}),
          MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})},
-    }),);
+    }));
 // clang-format on
 
 using OpSpecConstantInvalidTypeTest =
@@ -449,7 +449,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantInvalidValidType, OpSpecConstantInvalidTypeTest,
     ::testing::ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -474,14 +474,14 @@
         // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 const int64_t kMaxUnsigned48Bit = (int64_t(1) << 48) - 1;
 const int64_t kMaxSigned48Bit = (int64_t(1) << 47) - 1;
 const int64_t kMinSigned48Bit = -kMaxSigned48Bit - 1;
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         // 16 bit
@@ -522,9 +522,9 @@
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0\n",
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 1.79767e+308\n",
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -1.79767e+308\n",
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantHalfRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x0p+0\n",
@@ -557,11 +557,11 @@
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffp+16\n",   // -nan
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+16\n",  // -nan
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.004p+16\n",  // -nan
-    }), );
+    }));
 
 // clang-format off
 // (Clang-format really wants to break up these strings across lines.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantRoundTripNonFinite, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
   "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128\n",         // -inf
@@ -588,10 +588,10 @@
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0000000000001p+1024\n",   // -nan
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.00003p+1024\n",           // -nan
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.fffffffffffffp+1024\n",   // -nan
-    }),);
+    }));
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSpecConstantRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         // 16 bit
@@ -632,7 +632,7 @@
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 0\n",
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 1.79767e+308\n",
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 -1.79767e+308\n",
-    }), );
+    }));
 
 // Test OpSpecConstantOp
 
@@ -662,7 +662,7 @@
 #define CASE4(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6} }
 #define CASE5(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7} }
 #define CASE6(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7, 8} }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp, OpSpecConstantOpTestWithIds,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
         // Conversion
@@ -738,7 +738,7 @@
         CASE2(InBoundsPtrAccessChain),
         CASE3(InBoundsPtrAccessChain),
         CASE6(InBoundsPtrAccessChain),
-    }),);
+    }));
 #undef CASE1
 #undef CASE2
 #undef CASE3
@@ -768,7 +768,7 @@
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp,
     OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
@@ -783,7 +783,7 @@
         // composite, and then literal indices.
         {CASE(CompositeInsert), {0}},
         {CASE(CompositeInsert), {4, 3, 99, 1}},
-    }), );
+    }));
 
 using OpSpecConstantOpTestWithOneIdThenLiteralNumbers =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<SpvOp>>>;
@@ -806,7 +806,7 @@
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp,
     OpSpecConstantOpTestWithOneIdThenLiteralNumbers,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
@@ -814,7 +814,7 @@
         // indices.  Let's only test a few.
         {CASE(CompositeExtract), {0}},
         {CASE(CompositeExtract), {0, 99, 42, 16, 17, 12, 19}},
-    }), );
+    }));
 
 // TODO(dneto): OpConstantTrue
 // TODO(dneto): OpConstantFalse
diff --git a/test/text_to_binary.control_flow_test.cpp b/test/text_to_binary.control_flow_test.cpp
index 07f1108..01cc8e6 100644
--- a/test/text_to_binary.control_flow_test.cpp
+++ b/test/text_to_binary.control_flow_test.cpp
@@ -51,12 +51,12 @@
 
 // clang-format off
 #define CASE(VALUE,NAME) { SpvSelectionControl##VALUE, NAME}
-INSTANTIATE_TEST_CASE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
                         ValuesIn(std::vector<EnumCase<SpvSelectionControlMask>>{
                             CASE(MaskNone, "None"),
                             CASE(FlattenMask, "Flatten"),
                             CASE(DontFlattenMask, "DontFlatten"),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -95,7 +95,7 @@
   {                                       \
     SpvLoopControl##VALUE, NAME, { PARM } \
   }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryLoopMerge, OpLoopMergeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<int>>{
@@ -104,9 +104,9 @@
                 CASE(UnrollMask, "Unroll"),
                 CASE(DontUnrollMask, "DontUnroll"),
                 // clang-format on
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryLoopMergeV11, OpLoopMergeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<int>>{
@@ -116,7 +116,7 @@
                 {SpvLoopControlUnrollMask|SpvLoopControlDependencyLengthMask,
                       "DependencyLength|Unroll", {33}},
                 // clang-format on
-            })), );
+            })));
 #undef CASE
 #undef CASE1
 
@@ -251,7 +251,7 @@
                            Concatenate({{2, 3}, encoded_case_value, {4}}))})}};
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchValid1Word, OpSwitchValidTest,
     ValuesIn(std::vector<SwitchTestCase>({
         MakeSwitchTestCase(32, 0, "42", {42}, "100", {100}),
@@ -270,10 +270,10 @@
         MakeSwitchTestCase(16, 1, "0x8000", {0xffff8000}, "0x8100",
                            {0xffff8100}),
         MakeSwitchTestCase(16, 0, "0x8000", {0x00008000}, "0x8100", {0x8100}),
-    })), );
+    })));
 
 // NB: The words LOW ORDER bits show up first.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchValid2Words, OpSwitchValidTest,
     ValuesIn(std::vector<SwitchTestCase>({
         MakeSwitchTestCase(33, 0, "101", {101, 0}, "500", {500, 0}),
@@ -291,9 +291,9 @@
         MakeSwitchTestCase(63, 0, "0x500000000", {0, 5}, "12", {12, 0}),
         MakeSwitchTestCase(64, 0, "0x600000000", {0, 6}, "12", {12, 0}),
         MakeSwitchTestCase(64, 1, "0x700000123", {0x123, 7}, "12", {12, 0}),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSwitchRoundTripUnsignedIntegers, RoundTripTest,
     ValuesIn(std::vector<std::string>({
         // Unsigned 16-bit.
@@ -307,9 +307,9 @@
         // Unsigned 64-bit, three non-default cases.
         "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 9223372036854775807\n"
         "OpSwitch %2 %3 100 %4 102 %5 9000000000000000000 %6\n",
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSwitchRoundTripSignedIntegers, RoundTripTest,
     ValuesIn(std::vector<std::string>{
         // Signed 16-bit, with two non-default cases
@@ -332,7 +332,7 @@
         "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
         "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n"
         "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
-    }), );
+    }));
 
 using OpSwitchInvalidTypeTestCase =
     spvtest::TextToBinaryTestBase<TestWithParam<std::string>>;
@@ -349,7 +349,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchInvalidTests, OpSwitchInvalidTypeTestCase,
     ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -375,7 +375,7 @@
            // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 // TODO(dneto): OpPhi
diff --git a/test/text_to_binary.debug_test.cpp b/test/text_to_binary.debug_test.cpp
index b85650e..f9a4645 100644
--- a/test/text_to_binary.debug_test.cpp
+++ b/test/text_to_binary.debug_test.cpp
@@ -73,8 +73,8 @@
                                                GetParam().version})));
 }
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceTest,
-                        ::testing::ValuesIn(kLanguageCases), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceTest,
+                         ::testing::ValuesIn(kLanguageCases));
 
 TEST_F(OpSourceTest, WrongLanguage) {
   EXPECT_THAT(CompileFailure("OpSource xxyyzz 12345"),
@@ -113,9 +113,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceContinuedTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceContinuedTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 // Test OpSourceExtension
 
@@ -132,9 +132,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceExtensionTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceExtensionTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 TEST_F(TextToBinaryTest, OpLine) {
   EXPECT_THAT(CompiledInstructions("OpLine %srcfile 42 99"),
@@ -158,9 +158,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpStringTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpStringTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 using OpNameTest =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<const char*>>;
@@ -174,8 +174,8 @@
 
 // UTF-8, quoting, escaping, etc. are covered in the StringLiterals tests in
 // BinaryToText.Literal.cpp.
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpNameTest,
-                        ::testing::Values("", "foo bar this and that"), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpNameTest,
+                         ::testing::Values("", "foo bar this and that"));
 
 using OpMemberNameTest =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<const char*>>;
@@ -190,9 +190,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpMemberNameTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpMemberNameTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 // TODO(dneto): Parse failures?
 
@@ -207,8 +207,8 @@
       Eq(MakeInstruction(SpvOpModuleProcessed, MakeVector(GetParam()))));
 }
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpModuleProcessedTest,
-                        ::testing::Values("", "foo bar this and that"), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpModuleProcessedTest,
+                         ::testing::Values("", "foo bar this and that"));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.device_side_enqueue_test.cpp b/test/text_to_binary.device_side_enqueue_test.cpp
index 25c100b..03d7e74 100644
--- a/test/text_to_binary.device_side_enqueue_test.cpp
+++ b/test/text_to_binary.device_side_enqueue_test.cpp
@@ -49,7 +49,7 @@
                                  GetParam().local_size_operands)));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryTest, OpEnqueueKernelGood,
     ::testing::ValuesIn(std::vector<KernelEnqueueCase>{
         // Provide IDs for pointer-to-local arguments for the
@@ -71,7 +71,7 @@
          {13, 14, 15, 16, 17, 18, 19, 20, 21}},
         {"%l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 %l8 %l9",
          {13, 14, 15, 16, 17, 18, 19, 20, 21, 22}},
-    }), );
+    }));
 
 // Test some bad parses of OpEnqueueKernel.  For other cases, we're relying
 // on the uniformity of the parsing algorithm.  The following two tests, ensure
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 5c0bf98..84552b5 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -135,7 +135,7 @@
 
 // SPV_KHR_shader_ballot
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -164,9 +164,9 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMask\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMaskKHR})},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot_vulkan_1_1, ExtensionRoundTripTest,
     // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the
     // builtin enums.
@@ -194,11 +194,11 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMask\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMask})},
-            })), );
+            })));
 
 // The old builtin names (with KHR suffix) still work in the assmebler, and
 // map to the enums without the KHR.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot_vulkan_1_1_alias_check, ExtensionAssemblyTest,
     // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the
     // builtin enums.
@@ -219,11 +219,11 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMaskKHR\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMask})},
-            })), );
+            })));
 
 // SPV_KHR_shader_draw_parameters
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_draw_parameters, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -241,11 +241,11 @@
             {"OpDecorate %1 BuiltIn DrawIndex\n",
              MakeInstruction(SpvOpDecorate,
                              {1, SpvDecorationBuiltIn, SpvBuiltInDrawIndex})},
-        })), );
+        })));
 
 // SPV_KHR_subgroup_vote
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_subgroup_vote, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -260,11 +260,11 @@
                  MakeInstruction(SpvOpSubgroupAllKHR, {1, 2, 3})},
                 {"%2 = OpSubgroupAllEqualKHR %1 %3\n",
                  MakeInstruction(SpvOpSubgroupAllEqualKHR, {1, 2, 3})},
-            })), );
+            })));
 
 // SPV_KHR_16bit_storage
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_16bit_storage, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -276,11 +276,11 @@
                 {"OpCapability StorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageBuffer16BitAccess})},
-                {"OpCapability StorageUniform16\n",
+                {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(
                      SpvOpCapability,
                      {SpvCapabilityUniformAndStorageBuffer16BitAccess})},
-                {"OpCapability StorageUniform16\n",
+                {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageUniform16})},
                 {"OpCapability StoragePushConstant16\n",
@@ -289,9 +289,9 @@
                 {"OpCapability StorageInputOutput16\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageInputOutput16})},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_16bit_storage_alias_check, ExtensionAssemblyTest,
     Combine(ValuesIn(CommonVulkanEnvs()),
             ValuesIn(std::vector<AssemblyCase>{
@@ -303,11 +303,11 @@
                 {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageUniform16})},
-            })), );
+            })));
 
 // SPV_KHR_device_group
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_device_group, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -318,11 +318,11 @@
                 {"OpDecorate %1 BuiltIn DeviceIndex\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInDeviceIndex})},
-            })), );
+            })));
 
 // SPV_KHR_8bit_storage
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_8bit_storage, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -338,11 +338,11 @@
             {"OpCapability StoragePushConstant8\n",
              MakeInstruction(SpvOpCapability,
                              {SpvCapabilityStoragePushConstant8})},
-        })), );
+        })));
 
 // SPV_KHR_multiview
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_multiview, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -354,13 +354,13 @@
                 {"OpDecorate %1 BuiltIn ViewIndex\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInViewIndex})},
-            })), );
+            })));
 
 // SPV_AMD_shader_explicit_vertex_parameter
 
 #define PREAMBLE \
   "%1 = OpExtInstImport \"SPV_AMD_shader_explicit_vertex_parameter\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_explicit_vertex_parameter, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -374,13 +374,13 @@
                       SpvOpExtInstImport, {1},
                       MakeVector("SPV_AMD_shader_explicit_vertex_parameter")),
                   MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4, 5})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_AMD_shader_trinary_minmax
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_trinary_minmax\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_trinary_minmax, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -433,13 +433,13 @@
                  {MakeInstruction(SpvOpExtInstImport, {1},
                                   MakeVector("SPV_AMD_shader_trinary_minmax")),
                   MakeInstruction(SpvOpExtInst, {2, 3, 1, 9, 4, 5, 6})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_AMD_gcn_shader
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_gcn_shader\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_gcn_shader, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -458,13 +458,13 @@
                  Concatenate({MakeInstruction(SpvOpExtInstImport, {1},
                                               MakeVector("SPV_AMD_gcn_shader")),
                               MakeInstruction(SpvOpExtInst, {2, 3, 1, 3})})},
-            })), );
+            })));
 #undef PREAMBLE
 
 // SPV_AMD_shader_ballot
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_ballot\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_ballot, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -490,12 +490,12 @@
              Concatenate({MakeInstruction(SpvOpExtInstImport, {1},
                                           MakeVector("SPV_AMD_shader_ballot")),
                           MakeInstruction(SpvOpExtInst, {2, 3, 1, 4, 4})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_KHR_variable_pointers
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_variable_pointers, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -508,11 +508,11 @@
                 {"OpCapability VariablePointersStorageBuffer\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityVariablePointersStorageBuffer})},
-            })), );
+            })));
 
 // SPV_KHR_vulkan_memory_model
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_vulkan_memory_model, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -652,11 +652,15 @@
             // constant integer referenced by Id. There is no token for
             // them, and so no assembler or disassembler support required.
             // Similar for Scope ID.
-        })), );
+        })));
 
 // SPV_GOOGLE_decorate_string
 
-INSTANTIATE_TEST_CASE_P(
+// Now that OpDecorateString is the preferred spelling for
+// OpDecorateStringGOOGLE use that name in round trip tests, and the GOOGLE
+// name in an assembly-only test.
+
+INSTANTIATE_TEST_SUITE_P(
     SPV_GOOGLE_decorate_string, ExtensionRoundTripTest,
     Combine(
         // We'll get coverage over operand tables by trying the universal
@@ -664,6 +668,32 @@
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
         ValuesIn(std::vector<AssemblyCase>{
+            {"OpDecorateString %1 UserSemantic \"ABC\"\n",
+             MakeInstruction(SpvOpDecorateStringGOOGLE,
+                             {1, SpvDecorationHlslSemanticGOOGLE},
+                             MakeVector("ABC"))},
+            {"OpDecorateString %1 UserSemantic \"ABC\"\n",
+             MakeInstruction(SpvOpDecorateString,
+                             {1, SpvDecorationUserSemantic},
+                             MakeVector("ABC"))},
+            {"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
+             MakeInstruction(SpvOpMemberDecorateStringGOOGLE,
+                             {1, 3, SpvDecorationUserSemantic},
+                             MakeVector("DEF"))},
+            {"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
+             MakeInstruction(SpvOpMemberDecorateString,
+                             {1, 3, SpvDecorationUserSemantic},
+                             MakeVector("DEF"))},
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_GOOGLE_decorate_string, ExtensionAssemblyTest,
+    Combine(
+        // We'll get coverage over operand tables by trying the universal
+        // environments, and at least one specific environment.
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
+        ValuesIn(std::vector<AssemblyCase>{
             {"OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE \"ABC\"\n",
              MakeInstruction(SpvOpDecorateStringGOOGLE,
                              {1, SpvDecorationHlslSemanticGOOGLE},
@@ -672,11 +702,14 @@
              MakeInstruction(SpvOpMemberDecorateStringGOOGLE,
                              {1, 3, SpvDecorationHlslSemanticGOOGLE},
                              MakeVector("DEF"))},
-        })), );
+        })));
 
 // SPV_GOOGLE_hlsl_functionality1
 
-INSTANTIATE_TEST_CASE_P(
+// Now that CounterBuffer is the preferred spelling for HlslCounterBufferGOOGLE,
+// use that name in round trip tests, and the GOOGLE name in an assembly-only
+// test.
+INSTANTIATE_TEST_SUITE_P(
     SPV_GOOGLE_hlsl_functionality1, ExtensionRoundTripTest,
     Combine(
         // We'll get coverage over operand tables by trying the universal
@@ -686,14 +719,32 @@
         // HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since
         // they are coupled together.
         ValuesIn(std::vector<AssemblyCase>{
+            {"OpDecorateId %1 CounterBuffer %2\n",
+             MakeInstruction(SpvOpDecorateId,
+                             {1, SpvDecorationHlslCounterBufferGOOGLE, 2})},
+            {"OpDecorateId %1 CounterBuffer %2\n",
+             MakeInstruction(SpvOpDecorateId,
+                             {1, SpvDecorationCounterBuffer, 2})},
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_GOOGLE_hlsl_functionality1, ExtensionAssemblyTest,
+    Combine(
+        // We'll get coverage over operand tables by trying the universal
+        // environments, and at least one specific environment.
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
+        // HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since
+        // they are coupled together.
+        ValuesIn(std::vector<AssemblyCase>{
             {"OpDecorateId %1 HlslCounterBufferGOOGLE %2\n",
              MakeInstruction(SpvOpDecorateId,
                              {1, SpvDecorationHlslCounterBufferGOOGLE, 2})},
-        })), );
+        })));
 
 // SPV_NV_viewport_array2
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_NV_viewport_array2, ExtensionRoundTripTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                    SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3,
@@ -717,11 +768,11 @@
                 {"OpDecorate %1 BuiltIn ViewportMaskNV\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInViewportMaskNV})},
-            })), );
+            })));
 
 // SPV_NV_shader_subgroup_partitioned
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_NV_shader_subgroup_partitioned, ExtensionRoundTripTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1),
@@ -759,11 +810,11 @@
                               SpvGroupOperationPartitionedExclusiveScanNV, 4})},
             {"%2 = OpGroupIAdd %1 %3 PartitionedExclusiveScanNV %4\n",
              MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, 8, 4})},
-        })), );
+        })));
 
 // SPV_EXT_descriptor_indexing
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_EXT_descriptor_indexing, ExtensionRoundTripTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -851,7 +902,7 @@
              MakeInstruction(SpvOpDecorate, {1, SpvDecorationNonUniformEXT})},
             {"OpDecorate %1 NonUniformEXT\n",
              MakeInstruction(SpvOpDecorate, {1, 5300})},
-        })), );
+        })));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.function_test.cpp b/test/text_to_binary.function_test.cpp
index 748461f..55a8e6c 100644
--- a/test/text_to_binary.function_test.cpp
+++ b/test/text_to_binary.function_test.cpp
@@ -45,14 +45,14 @@
 
 // clang-format off
 #define CASE(VALUE,NAME) { SpvFunctionControl##VALUE, NAME }
-INSTANTIATE_TEST_CASE_P(TextToBinaryFunctionTest, OpFunctionControlTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryFunctionTest, OpFunctionControlTest,
                         ::testing::ValuesIn(std::vector<EnumCase<SpvFunctionControlMask>>{
                             CASE(MaskNone, "None"),
                             CASE(InlineMask, "Inline"),
                             CASE(DontInlineMask, "DontInline"),
                             CASE(PureMask, "Pure"),
                             CASE(ConstMask, "Const"),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.group_test.cpp b/test/text_to_binary.group_test.cpp
index 2f4b76d..becc3aa 100644
--- a/test/text_to_binary.group_test.cpp
+++ b/test/text_to_binary.group_test.cpp
@@ -44,12 +44,12 @@
 
 // clang-format off
 #define CASE(NAME) { SpvGroupOperation##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(TextToBinaryGroupOperation, GroupOperationTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryGroupOperation, GroupOperationTest,
                         ::testing::ValuesIn(std::vector<EnumCase<SpvGroupOperation>>{
                             CASE(Reduce),
                             CASE(InclusiveScan),
                             CASE(ExclusiveScan),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.image_test.cpp b/test/text_to_binary.image_test.cpp
index c1adedf..d445369 100644
--- a/test/text_to_binary.image_test.cpp
+++ b/test/text_to_binary.image_test.cpp
@@ -50,7 +50,7 @@
 }
 
 #define MASK(NAME) SpvImageOperands##NAME##Mask
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageOperandsAny, ImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // TODO(dneto): Rev32 adds many more values, and rearranges their
@@ -66,10 +66,10 @@
         {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
         {" Sample %5", {MASK(Sample), 5}},
         {" MinLod %5", {MASK(MinLod), 5}},
-    }), );
+    }));
 #undef MASK
 #define MASK(NAME) static_cast<uint32_t>(SpvImageOperands##NAME##Mask)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageOperandsCombination, ImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // TODO(dneto): Rev32 adds many more values, and rearranges their
@@ -95,7 +95,7 @@
          " %5 %6 %7 %8 %9 %10 %11 %12",
          {MASK(Bias) | MASK(Lod) | MASK(Grad) | MASK(ConstOffset) |
               MASK(Offset) | MASK(ConstOffsets) | MASK(Sample),
-          5, 6, 7, 8, 9, 10, 11, 12}}}), );
+          5, 6, 7, 8, 9, 10, 11, 12}}}));
 #undef MASK
 
 TEST_F(ImageOperandsTest, WrongOperand) {
@@ -173,24 +173,24 @@
 }
 
 #define MASK(NAME) SpvImageOperands##NAME##Mask
-INSTANTIATE_TEST_CASE_P(ImageSparseReadImageOperandsAny,
-                        ImageSparseReadImageOperandsTest,
-                        ::testing::ValuesIn(std::vector<ImageOperandsCase>{
-                            // Image operands are optional.
-                            {"", {}},
-                            // Test each kind, alone.
-                            {" Bias %5", {MASK(Bias), 5}},
-                            {" Lod %5", {MASK(Lod), 5}},
-                            {" Grad %5 %6", {MASK(Grad), 5, 6}},
-                            {" ConstOffset %5", {MASK(ConstOffset), 5}},
-                            {" Offset %5", {MASK(Offset), 5}},
-                            {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
-                            {" Sample %5", {MASK(Sample), 5}},
-                            {" MinLod %5", {MASK(MinLod), 5}},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ImageSparseReadImageOperandsAny,
+                         ImageSparseReadImageOperandsTest,
+                         ::testing::ValuesIn(std::vector<ImageOperandsCase>{
+                             // Image operands are optional.
+                             {"", {}},
+                             // Test each kind, alone.
+                             {" Bias %5", {MASK(Bias), 5}},
+                             {" Lod %5", {MASK(Lod), 5}},
+                             {" Grad %5 %6", {MASK(Grad), 5, 6}},
+                             {" ConstOffset %5", {MASK(ConstOffset), 5}},
+                             {" Offset %5", {MASK(Offset), 5}},
+                             {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
+                             {" Sample %5", {MASK(Sample), 5}},
+                             {" MinLod %5", {MASK(MinLod), 5}},
+                         }));
 #undef MASK
 #define MASK(NAME) static_cast<uint32_t>(SpvImageOperands##NAME##Mask)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageSparseReadImageOperandsCombination, ImageSparseReadImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // values.
@@ -212,7 +212,7 @@
           5, 6, 7, 8, 9, 10, 11, 12}},
         // Don't try the masks reversed, since this is a round trip test,
         // and the disassembler will sort them.
-    }), );
+    }));
 #undef MASK
 
 TEST_F(OpImageSparseReadTest, InvalidTypeOperand) {
diff --git a/test/text_to_binary.memory_test.cpp b/test/text_to_binary.memory_test.cpp
index ead08e6..c83c847 100644
--- a/test/text_to_binary.memory_test.cpp
+++ b/test/text_to_binary.memory_test.cpp
@@ -30,6 +30,7 @@
 using spvtest::MakeInstruction;
 using spvtest::TextToBinaryTest;
 using ::testing::Eq;
+using ::testing::HasSubstr;
 
 // Test assembly of Memory Access masks
 
@@ -45,14 +46,14 @@
                                  GetParam().operands())));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryMemoryAccessTest, MemoryAccessTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvMemoryAccessMask>>{
         {SpvMemoryAccessMaskNone, "None", {}},
         {SpvMemoryAccessVolatileMask, "Volatile", {}},
         {SpvMemoryAccessAlignedMask, "Aligned", {16}},
         {SpvMemoryAccessNontemporalMask, "Nontemporal", {}},
-    }), );
+    }));
 
 TEST_F(TextToBinaryTest, CombinedMemoryAccessMask) {
   const std::string input = "OpStore %ptr %value Volatile|Aligned 16";
@@ -76,7 +77,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvStorageClass##NAME, #NAME, {} }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryStorageClassTest, StorageClassTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvStorageClass>>{
         CASE(UniformConstant),
@@ -91,16 +92,330 @@
         CASE(PushConstant),
         CASE(AtomicCounter),
         CASE(Image),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
+using MemoryRoundTripTest = RoundTripTest;
+
+// OpPtrEqual appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrEqualGood) {
+  std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrEqual, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrEqualV13Bad) {
+  std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrEqual'"));
+}
+
+// OpPtrNotEqual appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrNotEqualGood) {
+  std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrNotEqual, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrNotEqualV13Bad) {
+  std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrNotEqual'"));
+}
+
+// OpPtrDiff appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrDiffGood) {
+  std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrDiff, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrDiffV13Good) {
+  // OpPtrDiff is enabled by a capability as well, so we can assemble
+  // it even in older SPIR-V environments.  We do that so we can
+  // write tests.
+  std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+}
+
+// OpCopyMemory
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryNoMemAccessGood) {
+  std::string spirv = "OpCopyMemory %1 %2\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTooFewArgsBad) {
+  std::string spirv = "OpCopyMemory %1\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTooManyArgsBad) {
+  std::string spirv = "OpCopyMemory %1 %2 %3\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%3'"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNoneGood) {
+  std::string spirv = "OpCopyMemory %1 %2 None\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 0})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVolatileGood) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAligned8Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Aligned 8\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 2, 8})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNontemporalGood) {
+  std::string spirv = "OpCopyMemory %1 %2 Nontemporal\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAvGood) {
+  std::string spirv = "OpCopyMemory %1 %2 MakePointerAvailableKHR %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 8, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVisGood) {
+  std::string spirv = "OpCopyMemory %1 %2 MakePointerVisibleKHR %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 16, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNonPrivateGood) {
+  std::string spirv = "OpCopyMemory %1 %2 NonPrivatePointerKHR\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 32})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessMixedGood) {
+  std::string spirv =
+      "OpCopyMemory %1 %2 "
+      "Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
+      "MakePointerVisibleKHR|NonPrivatePointerKHR 16 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 63, 16, 3, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV13Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
+  // Note: This will assemble but should not validate for SPIR-V 1.3
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV14Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessMixedV14Good) {
+  std::string spirv =
+      "OpCopyMemory %1 %2 Volatile|Nontemporal|"
+      "MakePointerVisibleKHR %3 "
+      "Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 21, 3, 42, 16, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+// OpCopyMemorySized
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedNoMemAccessGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooFewArgsBad) {
+  std::string spirv = "OpCopyMemorySized %1 %2\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooManyArgsBad) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 %4\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%4'"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNoneGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 None\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 0})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVolatileGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAligned8Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Aligned 8\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 2, 8})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNontemporalGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Nontemporal\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAvGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerAvailableKHR %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 8, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVisGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerVisibleKHR %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 16, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNonPrivateGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 NonPrivatePointerKHR\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 32})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessMixedGood) {
+  std::string spirv =
+      "OpCopyMemorySized %1 %2 %3 "
+      "Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
+      "MakePointerVisibleKHR|NonPrivatePointerKHR 16 %4 %5\n";
+  EXPECT_THAT(
+      CompiledInstructions(spirv),
+      Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 63, 16, 4, 5})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV13Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
+  // Note: This will assemble but should not validate for SPIR-V 1.3
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV14Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessMixedV14Good) {
+  std::string spirv =
+      "OpCopyMemorySized %1 %2 %3 Volatile|Nontemporal|"
+      "MakePointerVisibleKHR %4 "
+      "Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %5\n";
+  EXPECT_THAT(
+      CompiledInstructions(spirv),
+      Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 21, 4, 42, 16, 5})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
 // TODO(dneto): OpVariable with initializers
 // TODO(dneto): OpImageTexelPointer
 // TODO(dneto): OpLoad
 // TODO(dneto): OpStore
-// TODO(dneto): OpCopyMemory
-// TODO(dneto): OpCopyMemorySized
 // TODO(dneto): OpAccessChain
 // TODO(dneto): OpInBoundsAccessChain
 // TODO(dneto): OpPtrAccessChain
diff --git a/test/text_to_binary.mode_setting_test.cpp b/test/text_to_binary.mode_setting_test.cpp
index ed4fa2f..d1b69dd 100644
--- a/test/text_to_binary.mode_setting_test.cpp
+++ b/test/text_to_binary.mode_setting_test.cpp
@@ -69,7 +69,7 @@
         #MEMORY                                                          \
   }
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TextToBinaryMemoryModel, OpMemoryModelTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryMemoryModel, OpMemoryModelTest,
                         ValuesIn(std::vector<MemoryModelCase>{
                           // These cases exercise each addressing model, and
                           // each memory model, but not necessarily in
@@ -78,7 +78,7 @@
                             CASE(Logical,GLSL450),
                             CASE(Physical32,OpenCL),
                             CASE(Physical64,OpenCL),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -116,7 +116,7 @@
 
 // clang-format off
 #define CASE(NAME) SpvExecutionModel##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(TextToBinaryEntryPoint, OpEntryPointTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryEntryPoint, OpEntryPointTest,
                         ValuesIn(std::vector<EntryPointCase>{
                           { CASE(Vertex), "" },
                           { CASE(TessellationControl), "my tess" },
@@ -125,7 +125,7 @@
                           { CASE(Fragment), "FAT32" },
                           { CASE(GLCompute), "cubic" },
                           { CASE(Kernel), "Sanders" },
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -151,7 +151,7 @@
 }
 
 #define CASE(NAME) SpvExecutionMode##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryExecutionMode, OpExecutionModeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvExecutionMode>>{
@@ -188,16 +188,16 @@
                 {CASE(OutputTriangleStrip), {}},
                 {CASE(VecTypeHint), {96}},
                 {CASE(ContractionOff), {}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryExecutionModeV11, OpExecutionModeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvExecutionMode>>{
                 {CASE(Initializer)},
                 {CASE(Finalizer)},
                 {CASE(SubgroupSize), {12}},
-                {CASE(SubgroupsPerWorkgroup), {64}}})), );
+                {CASE(SubgroupsPerWorkgroup), {64}}})));
 #undef CASE
 
 TEST_F(OpExecutionModeTest, WrongMode) {
@@ -224,7 +224,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvCapability##NAME, #NAME }
-INSTANTIATE_TEST_CASE_P(TextToBinaryCapability, OpCapabilityTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryCapability, OpCapabilityTest,
                         ValuesIn(std::vector<EnumCase<SpvCapability>>{
                             CASE(Matrix),
                             CASE(Shader),
@@ -280,7 +280,7 @@
                             CASE(DerivativeControl),
                             CASE(InterpolationFunction),
                             CASE(TransformFeedback),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.type_declaration_test.cpp b/test/text_to_binary.type_declaration_test.cpp
index c6f158f..1589188 100644
--- a/test/text_to_binary.type_declaration_test.cpp
+++ b/test/text_to_binary.type_declaration_test.cpp
@@ -48,7 +48,7 @@
 
 // clang-format off
 #define CASE(NAME) {SpvDim##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDim, DimTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvDim>>{
         CASE(1D),
@@ -58,7 +58,7 @@
         CASE(Rect),
         CASE(Buffer),
         CASE(SubpassData),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -84,7 +84,7 @@
 
 // clang-format off
 #define CASE(NAME) {SpvImageFormat##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageFormat, ImageFormatTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvImageFormat>>{
         CASE(Unknown),
@@ -127,7 +127,7 @@
         CASE(Rg8ui),
         CASE(R16ui),
         CASE(R8ui),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -153,13 +153,13 @@
 
 // clang-format off
 #define CASE(NAME) {SpvAccessQualifier##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AccessQualifier, ImageAccessQualifierTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvAccessQualifier>>{
       CASE(ReadOnly),
       CASE(WriteOnly),
       CASE(ReadWrite),
-    }),);
+    }));
 // clang-format on
 #undef CASE
 
@@ -178,13 +178,13 @@
 
 // clang-format off
 #define CASE(NAME) {SpvAccessQualifier##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryTypePipe, OpTypePipeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvAccessQualifier>>{
                             CASE(ReadOnly),
                             CASE(WriteOnly),
                             CASE(ReadWrite),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary_test.cpp b/test/text_to_binary_test.cpp
index 4ba37ad..57f0a6c 100644
--- a/test/text_to_binary_test.cpp
+++ b/test/text_to_binary_test.cpp
@@ -58,7 +58,7 @@
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ParseMask, GoodMaskParseTest,
     ::testing::ValuesIn(std::vector<MaskCase>{
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0, "None"},
@@ -87,7 +87,7 @@
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 4, "Pure"},
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 8, "Const"},
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0xd, "Inline|Const|Pure"},
-    }), );
+    }));
 
 using BadFPFastMathMaskParseTest = ::testing::TestWithParam<const char*>;
 
@@ -102,12 +102,12 @@
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(ParseMask, BadFPFastMathMaskParseTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            nullptr, "", "NotValidEnum", "|", "NotInf|",
-                            "|NotInf", "NotInf||NotNaN",
-                            "Unroll"  // A good word, but for the wrong enum
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ParseMask, BadFPFastMathMaskParseTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             nullptr, "", "NotValidEnum", "|", "NotInf|",
+                             "|NotInf", "NotInf||NotNaN",
+                             "Unroll"  // A good word, but for the wrong enum
+                         }));
 
 TEST_F(TextToBinaryTest, InvalidText) {
   ASSERT_EQ(SPV_ERROR_INVALID_TEXT,
@@ -197,7 +197,7 @@
                                               {1, 2, GetParam().second})})));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatValues, TextToBinaryFloatValueTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, uint32_t>>{
         {"0.0", 0x00000000},          // +0
@@ -213,7 +213,7 @@
         {"-2.5", 0xc0200000},
         {"!0xff800000", 0xff800000},  // -inf
         {"!0xff800001", 0xff800001},  // NaN
-    }), );
+    }));
 
 using TextToBinaryHalfValueTest = spvtest::TextToBinaryTestBase<
     ::testing::TestWithParam<std::pair<std::string, uint32_t>>>;
@@ -227,7 +227,7 @@
                                               {1, 2, GetParam().second})})));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     HalfValues, TextToBinaryHalfValueTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, uint32_t>>{
         {"0.0", 0x00000000},
@@ -245,7 +245,7 @@
         {"0x1.8p4", 0x00004e00},
         {"0x1.801p4", 0x00004e00},
         {"0x1.804p4", 0x00004e01},
-    }), );
+    }));
 
 TEST(CreateContext, InvalidEnvironment) {
   spv_target_env env;
diff --git a/test/timer_test.cpp b/test/timer_test.cpp
index e53af66..84ab46d 100644
--- a/test/timer_test.cpp
+++ b/test/timer_test.cpp
@@ -97,7 +97,7 @@
   long PageFault() const override { return count_stop_ * 3600L; }
 
   // Calling Stop() does nothing but just increases |count_stop_| by 1.
-  void Stop() override { ++count_stop_; };
+  void Stop() override { ++count_stop_; }
 
  private:
   unsigned int count_stop_;
diff --git a/test/tools/expect.py b/test/tools/expect.py
index c959650..e21a0c4 100755
--- a/test/tools/expect.py
+++ b/test/tools/expect.py
@@ -18,15 +18,20 @@
 methods in the mixin classes.
 """
 import difflib
+import functools
 import os
 import re
 import subprocess
+import traceback
 from spirv_test_framework import SpirvTest
+from builtins import bytes
 
+DEFAULT_SPIRV_VERSION = 0x010000
 
 def convert_to_unix_line_endings(source):
   """Converts all line endings in source to be unix line endings."""
-  return source.replace('\r\n', '\n').replace('\r', '\n')
+  result = source.replace('\r\n', '\n').replace('\r', '\n')
+  return result
 
 
 def substitute_file_extension(filename, extension):
@@ -74,6 +79,15 @@
     return True, ''
 
 
+class ReturnCodeIsNonZero(SpirvTest):
+  """Mixin class for checking that the return code is not zero."""
+
+  def check_return_code_is_nonzero(self, status):
+    if not status.returncode:
+      return False, 'return code is 0'
+    return True, ''
+
+
 class NoOutputOnStdout(SpirvTest):
   """Mixin class for checking that there is no output on stdout."""
 
@@ -133,7 +147,7 @@
       word = binary[index * 4:(index + 1) * 4]
       if little_endian:
         word = reversed(word)
-      return reduce(lambda w, b: (w << 8) | ord(b), word, 0)
+      return functools.reduce(lambda w, b: (w << 8) | b, word, 0)
 
     def check_endianness(binary):
       """Checks the endianness of the given SPIR-V binary.
@@ -169,7 +183,7 @@
     # profile
 
     if version != spv_version and version != 0:
-      return False, 'Incorrect SPV binary: wrong version number'
+      return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version)
     # Shaderc-over-Glslang (0x000d....) or
     # SPIRV-Tools (0x0007....) generator number
     if read_word(preamble, 2, little_endian) != 0x000d0007 and \
@@ -185,7 +199,9 @@
 class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble):
   """Provides methods for verifying preamble for a SPV object file."""
 
-  def verify_object_file_preamble(self, filename, spv_version=0x10000):
+  def verify_object_file_preamble(self,
+                                  filename,
+                                  spv_version=DEFAULT_SPIRV_VERSION):
     """Checks that the given SPIR-V binary file has correct preamble."""
 
     success, message = verify_file_non_empty(filename)
@@ -254,6 +270,21 @@
     return True, ''
 
 
+class ValidObjectFile1_4(ReturnCodeIsZero, CorrectObjectFilePreamble):
+  """Mixin class for checking that every input file generates a valid SPIR-V 1.4
+    object file following the object file naming rule, and there is no output on
+    stdout/stderr."""
+
+  def check_object_file_preamble(self, status):
+    for input_filename in status.input_filenames:
+      object_filename = get_object_filename(input_filename)
+      success, message = self.verify_object_file_preamble(
+          os.path.join(status.directory, object_filename), 0x10400)
+      if not success:
+        return False, message
+    return True, ''
+
+
 class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
                                         CorrectObjectFilePreamble):
   """Mixin class for checking that every input file generates a valid object
@@ -479,7 +510,7 @@
     if not status.stderr:
       return False, 'Expected error message, but no output on stderr'
     if self.expected_error_substr not in convert_to_unix_line_endings(
-        status.stderr):
+        status.stderr.decode('utf8')):
       return False, ('Incorrect stderr output:\n{act}\n'
                      'Expected substring not found in stderr:\n{exp}'.format(
                          act=status.stderr, exp=self.expected_error_substr))
@@ -499,7 +530,7 @@
                      ' command execution')
     if not status.stderr:
       return False, 'Expected warning message, but no output on stderr'
-    if self.expected_warning != convert_to_unix_line_endings(status.stderr):
+    if self.expected_warning != convert_to_unix_line_endings(status.stderr.decode('utf8')):
       return False, ('Incorrect stderr output:\n{act}\n'
                      'Expected:\n{exp}'.format(
                          act=status.stderr, exp=self.expected_warning))
@@ -559,16 +590,16 @@
       if not status.stdout:
         return False, 'Expected something on stdout'
     elif type(self.expected_stdout) == str:
-      if self.expected_stdout != convert_to_unix_line_endings(status.stdout):
+      if self.expected_stdout != convert_to_unix_line_endings(status.stdout.decode('utf8')):
         return False, ('Incorrect stdout output:\n{ac}\n'
                        'Expected:\n{ex}'.format(
                            ac=status.stdout, ex=self.expected_stdout))
     else:
-      if not self.expected_stdout.search(
-          convert_to_unix_line_endings(status.stdout)):
+      converted = convert_to_unix_line_endings(status.stdout.decode('utf8'))
+      if not self.expected_stdout.search(converted):
         return False, ('Incorrect stdout output:\n{ac}\n'
                        'Expected to match regex:\n{ex}'.format(
-                           ac=status.stdout, ex=self.expected_stdout.pattern))
+                           ac=status.stdout.decode('utf8'), ex=self.expected_stdout.pattern))
     return True, ''
 
 
@@ -593,13 +624,13 @@
       if not status.stderr:
         return False, 'Expected something on stderr'
     elif type(self.expected_stderr) == str:
-      if self.expected_stderr != convert_to_unix_line_endings(status.stderr):
+      if self.expected_stderr != convert_to_unix_line_endings(status.stderr.decode('utf8')):
         return False, ('Incorrect stderr output:\n{ac}\n'
                        'Expected:\n{ex}'.format(
                            ac=status.stderr, ex=self.expected_stderr))
     else:
       if not self.expected_stderr.search(
-          convert_to_unix_line_endings(status.stderr)):
+          convert_to_unix_line_endings(status.stderr.decode('utf8'))):
         return False, ('Incorrect stderr output:\n{ac}\n'
                        'Expected to match regex:\n{ex}'.format(
                            ac=status.stderr, ex=self.expected_stderr.pattern))
@@ -664,7 +695,7 @@
     # Collect all the output lines containing a pass name.
     pass_names = []
     pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)')
-    for line in status.stderr.splitlines():
+    for line in status.stderr.decode('utf8').splitlines():
       match = pass_name_re.match(line)
       if match:
         pass_names.append(match.group('pass_name'))
diff --git a/test/tools/opt/flags.py b/test/tools/opt/flags.py
index 69462fc..a89477c 100644
--- a/test/tools/opt/flags.py
+++ b/test/tools/opt/flags.py
@@ -34,7 +34,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptBase')
-class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_3):
+class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_4):
   """Tests that spirv-opt accepts a SPIR-V object file."""
 
   shader = placeholder.FileSPIRVShader(empty_main_assembly(), '.spvasm')
@@ -52,14 +52,14 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestValidPassFlags(expect.ValidObjectFile1_3,
+class TestValidPassFlags(expect.ValidObjectFile1_4,
                          expect.ExecutedListOfPasses):
   """Tests that spirv-opt accepts all valid optimization flags."""
 
   flags = [
       '--ccp', '--cfg-cleanup', '--combine-access-chains', '--compact-ids',
       '--convert-local-access-chains', '--copy-propagate-arrays',
-      '--eliminate-common-uniform', '--eliminate-dead-branches',
+      '--eliminate-dead-branches',
       '--eliminate-dead-code-aggressive', '--eliminate-dead-const',
       '--eliminate-dead-functions', '--eliminate-dead-inserts',
       '--eliminate-dead-variables', '--eliminate-insert-extract',
@@ -82,7 +82,6 @@
       'compact-ids',
       'convert-local-access-chains',
       'copy-propagate-arrays',
-      'eliminate-common-uniform',
       'eliminate-dead-branches',
       'eliminate-dead-code-aggressive',
       'eliminate-dead-const',
@@ -129,7 +128,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_3,
+class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_4,
                                         expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -O."""
 
@@ -176,7 +175,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestSizeOptimizationPasses(expect.ValidObjectFile1_3,
+class TestSizeOptimizationPasses(expect.ValidObjectFile1_4,
                                  expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -Os."""
 
@@ -215,7 +214,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestLegalizationPasses(expect.ValidObjectFile1_3,
+class TestLegalizationPasses(expect.ValidObjectFile1_4,
                              expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by --legalize-hlsl.
   """
@@ -227,6 +226,7 @@
       'inline-entry-points-exhaustive',
       'eliminate-dead-functions',
       'private-to-local',
+      'fix-storage-class',
       'eliminate-local-single-block',
       'eliminate-local-single-store',
       'eliminate-dead-code-aggressive',
@@ -331,3 +331,45 @@
 
   spirv_args = ['--loop-peeling-threshold=a10f']
   expected_error_substr = 'must have a positive integer argument'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestWebGPUToVulkanThenVulkanToWebGPUIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests Vulkan->WebGPU flag cannot be used after WebGPU->Vulkan flag."""
+
+  spirv_args = ['--webgpu-to-vulkan', '--vulkan-to-webgpu']
+  expected_error_substr = 'Cannot use both'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestVulkanToWebGPUThenWebGPUToVulkanIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests WebGPU->Vulkan flag cannot be used after Vulkan->WebGPU flag."""
+
+  spirv_args = ['--vulkan-to-webgpu', '--webgpu-to-vulkan']
+  expected_error_substr = 'Cannot use both'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestTargetEnvThenVulkanToWebGPUIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests Vulkan->WebGPU flag cannot be used after target env flag."""
+
+  spirv_args = ['--target-env=opengl4.0', '--vulkan-to-webgpu']
+  expected_error_substr = 'defines the target environment'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestVulkanToWebGPUThenTargetEnvIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests target env flag cannot be used after Vulkan->WebGPU flag."""
+
+  spirv_args = ['--vulkan-to-webgpu', '--target-env=opengl4.0']
+  expected_error_substr = 'defines the target environment'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestTargetEnvThenWebGPUToVulkanIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests WebGPU->Vulkan flag cannot be used after target env flag."""
+
+  spirv_args = ['--target-env=opengl4.0', '--webgpu-to-vulkan']
+  expected_error_substr = 'defines the target environment'
+
+@inside_spirv_testsuite('SpirvOptFlags')
+class TestWebGPUToVulkanThenTargetEnvIsInvalid(expect.ReturnCodeIsNonZero, expect.ErrorMessageSubstr):
+  """Tests target env flag cannot be used after WebGPU->Vulkan flag."""
+
+  spirv_args = ['--webgpu-to-vulkan', '--target-env=opengl4.0']
+  expected_error_substr = 'defines the target environment'
diff --git a/test/tools/spirv_test_framework.py b/test/tools/spirv_test_framework.py
index 03ad08f..d8d64f3 100755
--- a/test/tools/spirv_test_framework.py
+++ b/test/tools/spirv_test_framework.py
@@ -44,8 +44,6 @@
 be deleted.
 """
 
-from __future__ import print_function
-
 import argparse
 import fnmatch
 import inspect
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index d478a7d..b52c764 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -15,6 +15,8 @@
 set(VAL_TEST_COMMON_SRCS
   ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h
   ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/val_code_generator.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val_code_generator.h
   ${CMAKE_CURRENT_SOURCE_DIR}/val_fixtures.h
 )
 
@@ -46,10 +48,12 @@
   SRCS val_limits_test.cpp
        ${VAL_TEST_COMMON_SRCS}
   LIBS ${SPIRV_TOOLS}
+  PCH_FILE pch_test_val
 )
 
-add_spvtools_unittest(TARGET val_ijklmnop
+add_spvtools_unittest(TARGET val_fghijklmnop
   SRCS
+       val_function_test.cpp
        val_id_test.cpp
        val_image_test.cpp
        val_interfaces_test.cpp
@@ -57,8 +61,10 @@
        val_literals_test.cpp
        val_logicals_test.cpp
        val_memory_test.cpp
+       val_misc_test.cpp
        val_modes_test.cpp
        val_non_uniform_test.cpp
+       val_opencl_test.cpp
        val_primitives_test.cpp
        ${VAL_TEST_COMMON_SRCS}
   LIBS ${SPIRV_TOOLS}
@@ -67,6 +73,7 @@
 
 add_spvtools_unittest(TARGET val_stuvw
   SRCS
+       val_small_type_uses_test.cpp
        val_ssa_test.cpp
        val_state_test.cpp
        val_storage_test.cpp
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index 87e006c..b82fc97 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -1165,6 +1165,150 @@
                 "vector size of the right operand: OuterProduct"));
 }
 
+std::string GenerateCoopMatCode(const std::string& extra_types,
+                                const std::string& main_body) {
+  const std::string prefix =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_16 = OpConstant %u32 16
+%u32_4 = OpConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%u32mat = OpTypeCooperativeMatrixNV %u32 %subgroup %u32_8 %u32_8
+%s32mat = OpTypeCooperativeMatrixNV %s32 %subgroup %u32_8 %u32_8
+
+%f16_1 = OpConstant %f16 1
+%f32_1 = OpConstant %f32 1
+%u32_1 = OpConstant %u32 1
+%s32_1 = OpConstant %s32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+%u32mat_1 = OpConstantComposite %u32mat %u32_1
+%s32mat_1 = OpConstantComposite %s32mat %s32_1
+
+%u32_c1 = OpSpecConstant %u32 1
+%u32_c2 = OpSpecConstant %u32 2
+
+%f16matc = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_c1 %u32_c2
+%f16matc_1 = OpConstantComposite %f16matc %f16_1
+
+%mat16x4 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_16 %u32_4
+%mat4x16 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_4 %u32_16
+%mat16x16 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_16 %u32_16
+%f16mat_16x4_1 = OpConstantComposite %mat16x4 %f16_1
+%f16mat_4x16_1 = OpConstantComposite %mat4x16 %f16_1
+%f16mat_16x16_1 = OpConstantComposite %mat16x16 %f16_1)";
+
+  const std::string func_begin =
+      R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string suffix =
+      R"(
+OpReturn
+OpFunctionEnd)";
+
+  return prefix + extra_types + func_begin + main_body + suffix;
+}
+
+TEST_F(ValidateArithmetics, CoopMatSuccess) {
+  const std::string body = R"(
+%val1 = OpFAdd %f16mat %f16mat_1 %f16mat_1
+%val2 = OpFSub %f16mat %f16mat_1 %f16mat_1
+%val3 = OpFDiv %f16mat %f16mat_1 %f16mat_1
+%val4 = OpFNegate %f16mat %f16mat_1
+%val5 = OpIAdd %u32mat %u32mat_1 %u32mat_1
+%val6 = OpISub %u32mat %u32mat_1 %u32mat_1
+%val7 = OpUDiv %u32mat %u32mat_1 %u32mat_1
+%val8 = OpIAdd %s32mat %s32mat_1 %s32mat_1
+%val9 = OpISub %s32mat %s32mat_1 %s32mat_1
+%val10 = OpSDiv %s32mat %s32mat_1 %s32mat_1
+%val11 = OpSNegate %s32mat %s32mat_1
+%val12 = OpMatrixTimesScalar %f16mat %f16mat_1 %f16_1
+%val13 = OpMatrixTimesScalar %u32mat %u32mat_1 %u32_1
+%val14 = OpMatrixTimesScalar %s32mat %s32mat_1 %s32_1
+%val15 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_16x4_1 %f16mat_4x16_1 %f16mat_16x16_1
+%val16 = OpCooperativeMatrixMulAddNV %f16matc %f16matc_1 %f16matc_1 %f16matc_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, CoopMatFMulFail) {
+  const std::string body = R"(
+%val1 = OpFMul %f16mat %f16mat_1 %f16mat_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected floating scalar or vector type as Result Type: FMul"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatMatrixTimesScalarMismatchFail) {
+  const std::string body = R"(
+%val1 = OpMatrixTimesScalar %f16mat %f16mat_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar operand type to be equal to the component "
+                "type of the matrix operand: MatrixTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatScopeFail) {
+  const std::string types = R"(
+%workgroup = OpConstant %u32 2
+
+%mat16x16_wg = OpTypeCooperativeMatrixNV %f16 %workgroup %u32_16 %u32_16
+%f16matwg_16x16_1 = OpConstantComposite %mat16x16_wg %f16_1
+)";
+
+  const std::string body = R"(
+%val1 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_16x4_1 %f16mat_4x16_1 %f16matwg_16x16_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Cooperative matrix scopes must match: CooperativeMatrixMulAddNV"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatDimFail) {
+  const std::string body = R"(
+%val1 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_4x16_1 %f16mat_16x4_1 %f16mat_16x16_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cooperative matrix 'M' mismatch: CooperativeMatrixMulAddNV"));
+}
+
 TEST_F(ValidateArithmetics, IAddCarrySuccess) {
   const std::string body = R"(
 %val1 = OpIAddCarry %struct_u32_u32 %u32_0 %u32_1
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
index 1f30018..15887eb 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -190,6 +190,9 @@
 %f32_ptr_uniformconstant = OpTypePointer UniformConstant %f32
 %f32_uc_var = OpVariable %f32_ptr_uniformconstant UniformConstant
 
+%f32_ptr_image = OpTypePointer Image %f32
+%f32_im_var = OpVariable %f32_ptr_image Image
+
 %main = OpFunction %void None %func
 %main_entry = OpLabel
 )";
@@ -225,7 +228,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateAtomics, AtomicLoadVulkanSuccess) {
+TEST_F(ValidateAtomics, AtomicLoadInt32VulkanSuccess) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
@@ -235,6 +238,41 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadFloatVulkan) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_var %device %relaxed
+%val2 = OpAtomicLoad %f32 %f32_var %workgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected Result Type to be int scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadInt64WithCapabilityVulkanSuccess) {
+  const std::string body = R"(
+  %val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
+  %val2 = OpAtomicLoad %u64 %u64_var %workgroup %acquire
+  )";
+
+  CompileSuccessfully(GenerateShaderCode(body, "OpCapability Int64Atomics\n"),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadInt64WithoutCapabilityVulkan) {
+  const std::string body = R"(
+  %val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
+  %val2 = OpAtomicLoad %u64 %u64_var %workgroup %acquire
+  )";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("64-bit atomics require the Int64Atomics capability"));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreOpenCLFunctionPointerStorageTypeSuccess) {
   const std::string body = R"(
 %f32_var_function = OpVariable %f32_ptr_function Function
@@ -253,11 +291,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicStore: expected Pointer Storage Class to be Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image or "
-                "StorageBuffer"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicStore: Function storage class forbidden when "
+                        "the Shader capability is declared."));
 }
 
 // TODO(atgoo@github.com): the corresponding check fails Vulkan CTS,
@@ -338,30 +374,37 @@
           "AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
 }
 
-TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
+TEST_F(ValidateAtomics, AtomicLoadWebGPUSuccess) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
-%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
-TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSequentiallyConsistentFailure) {
+TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) {
   const std::string body = R"(
-%val3 = OpAtomicLoad %u32 %u32_var %subgroup %sequentially_consistent
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %acquire
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %release
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "WebGPU spec disallows any bit masks in Memory Semantics that are "
-          "not Acquire, Release, AcquireRelease, UniformMemory, "
-          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
-          "MakeVisibleKHR\n  %34 = OpAtomicLoad %uint %29 %uint_3 %uint_16\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) {
+  const std::string body = R"(
+%val3 = OpAtomicLoad %u32 %u32_var %invocation %sequentially_consistent
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
 }
 
 TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
@@ -458,8 +501,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicLoad: expected Memory Scope to be a 32-bit int\n  %40 = "
-                "OpAtomicLoad %float %28 %float_1 %uint_0_1\n"));
+      HasSubstr("AtomicLoad: expected Memory Scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicLoadWrongMemorySemanticsType) {
@@ -544,13 +586,24 @@
 
 TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
   const std::string body = R"(
-OpAtomicStore %u32_var %queuefamily %release %u32_1
+OpAtomicStore %u32_var %queuefamily %relaxed %u32_1
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
+TEST_F(ValidateAtomics, AtomicStoreWebGPUNonRelaxedFailure) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %queuefamily %release %u32_1
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
   const std::string body = R"(
 OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
@@ -558,14 +611,8 @@
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "WebGPU spec disallows any bit masks in Memory Semantics that are "
-          "not Acquire, Release, AcquireRelease, UniformMemory, "
-          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
-          "MakeVisibleKHR\n"
-          "  OpAtomicStore %29 %uint_5 %uint_16 %uint_1\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
@@ -594,6 +641,19 @@
                 "type"));
 }
 
+TEST_F(ValidateAtomics, AtomicStoreWrongPointerStorageTypeForOpenCL) {
+  const std::string body = R"(
+OpAtomicStore %f32_im_var %device %relaxed %f32_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: storage class must be Function, Workgroup, "
+                "CrossWorkGroup or Generic in the OpenCL environment."));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerStorageType) {
   const std::string body = R"(
 OpAtomicStore %f32_uc_var %device %relaxed %f32_1
@@ -601,11 +661,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicStore: expected Pointer Storage Class to be Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image or "
-                "StorageBuffer"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicStore: storage class forbidden by universal "
+                        "validation rules."));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongScopeType) {
@@ -731,9 +789,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "AtomicExchange: expected Memory Scope to be a 32-bit int\n  %40 = "
-          "OpAtomicExchange %float %28 %float_1 %uint_0_1 %float_0\n"));
+      HasSubstr("AtomicExchange: expected Memory Scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicExchangeWrongMemorySemanticsType) {
@@ -848,8 +904,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicCompareExchange: expected Memory Scope to be a 32-bit "
-                "int\n  %40 = OpAtomicCompareExchange %float %28 %float_1 "
-                "%uint_0_1 %uint_0_1 %float_0 %float_0\n"));
+                "int"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType1) {
@@ -1030,8 +1085,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "AtomicFlagTestAndSet: expected Memory Scope to be a 32-bit int\n  "
-          "%40 = OpAtomicFlagTestAndSet %bool %30 %ulong_1 %uint_0_1\n"));
+          "AtomicFlagTestAndSet: expected Memory Scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongMemorySemanticsType) {
@@ -1132,8 +1186,7 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicIIncrement: Memory Semantics can have at most "
                         "one of the following bits set: Acquire, Release, "
-                        "AcquireRelease or SequentiallyConsistent\n  %40 = "
-                        "OpAtomicIIncrement %uint %30 %uint_1_0 %uint_6\n"));
+                        "AcquireRelease or SequentiallyConsistent"));
 }
 
 TEST_F(ValidateAtomics, AtomicUniformMemorySemanticsShader) {
@@ -1869,7 +1922,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
-                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "Workgroup, Invocation, and QueueFamilyKHR\n"
                 "  %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n"));
 }
 
@@ -1883,7 +1936,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
-                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "Workgroup, Invocation, and QueueFamilyKHR\n"
                 "  %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n"));
 }
 
@@ -1896,27 +1949,27 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
-TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeGood) {
+TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %subgroup %relaxed
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
-}
-
-TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) {
-  const std::string body = R"(
-%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
-)";
-
-  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
-                "Workgroup, Subgroup and QueuFamilyKHR\n"
-                "  %34 = OpAtomicLoad %uint %29 %uint_4 %uint_0_1\n"));
+                "Workgroup, Invocation, and QueueFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_3 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
 TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) {
@@ -1928,6 +1981,176 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
+TEST_F(ValidateAtomics, CompareExchangeWeakV13ValV14Good) {
+  const std::string body = R"(
+%val1 = OpAtomicCompareExchangeWeak %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateAtomics, CompareExchangeWeakV14Bad) {
+  const std::string body = R"(
+%val1 = OpAtomicCompareExchangeWeak %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicCompareExchangeWeak requires SPIR-V version 1.3 or earlier"));
+}
+
+TEST_F(ValidateAtomics, CompareExchangeVolatileMatch) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%volatile = OpConstant %int 32768
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%cmp_ex = OpAtomicCompareExchange %int %wg_var %workgroup %volatile %volatile %int_0 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, CompareExchangeVolatileMismatch) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%volatile = OpConstant %int 32768
+%non_volatile = OpConstant %int 0
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%cmp_ex = OpAtomicCompareExchange %int %wg_var %workgroup %non_volatile %volatile %int_0 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Volatile mask setting must match for Equal and "
+                        "Unequal memory semantics"));
+}
+
+TEST_F(ValidateAtomics, CompareExchangeVolatileMismatchCooperativeMatrix) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability Linkage
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%volatile = OpSpecConstant %int 32768
+%non_volatile = OpSpecConstant %int 32768
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%cmp_ex = OpAtomicCompareExchange %int %wg_var %workgroup %volatile %non_volatile %int_0 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  // This is ok because we cannot evaluate the spec constant defaults.
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, VolatileRequiresVulkanMemoryModel) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%volatile = OpConstant %int 32768
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicLoad %int %wg_var %workgroup %volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory Semantics Volatile requires capability "
+                        "VulkanMemoryModelKHR"));
+}
+
+TEST_F(ValidateAtomics, CooperativeMatrixSemanticsMustBeConstant) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%undef = OpUndef %int
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpAtomicLoad %int %wg_var %workgroup %undef
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory Semantics must be a constant instruction when "
+                        "CooperativeMatrixNV capability is present"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 264d130..2214197 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -78,9 +78,13 @@
 %acquire_and_release = OpConstant %u32 6
 %sequentially_consistent = OpConstant %u32 16
 %acquire_release_uniform_workgroup = OpConstant %u32 328
+%acquire_uniform_workgroup = OpConstant %u32 322
+%release_uniform_workgroup = OpConstant %u32 324
 %acquire_and_release_uniform = OpConstant %u32 70
 %acquire_release_subgroup = OpConstant %u32 136
 %uniform = OpConstant %u32 64
+%uniform_workgroup = OpConstant %u32 320
+
 
 %main = OpFunction %void None %func
 %main_entry = OpLabel
@@ -245,9 +249,8 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierWebGPUSuccess) {
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireReleaseSuccess) {
   const std::string body = R"(
-OpControlBarrier %workgroup %queuefamily %none
 OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
 )";
 
@@ -255,6 +258,41 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierWebGPURelaxedFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires AcquireRelease to set"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %acquire_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUReleaseFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) {
   const std::string body = R"(
 OpControlBarrier %device %device %none
@@ -365,7 +403,7 @@
                         "is limited to Workgroup and Subgroup"));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeDevice) {
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeDeviceBad) {
   const std::string body = R"(
 OpControlBarrier %device %workgroup %none
 )";
@@ -374,7 +412,19 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ControlBarrier: in WebGPU environment Execution Scope "
-                        "is limited to Workgroup and Subgroup"));
+                        "is limited to Workgroup"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeSubgroupBad) {
+  const std::string body = R"(
+OpControlBarrier %subgroup %workgroup %none
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: in WebGPU environment Execution Scope "
+                        "is limited to Workgroup"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) {
@@ -630,6 +680,50 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseSuccess) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPURelaxedFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires AcquireRelease to set"));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %acquire_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUReleaseFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
 TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) {
   const std::string body = R"(
 OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup
@@ -872,8 +966,8 @@
 OpMemoryBarrier %u32 %u32_0
 )";
 
-  CompileSuccessfully(GenerateKernelCode(body));
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
   EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%uint] cannot be a "
                                                "type"));
 }
@@ -1279,6 +1373,115 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateBarriers, VolatileMemoryBarrier) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 32768
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpMemoryBarrier %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory Semantics Volatile can only be used with "
+                        "atomic instructions"));
+}
+
+TEST_F(ValidateBarriers, VolatileControlBarrier) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpConstant %int 32768
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpControlBarrier %device %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory Semantics Volatile can only be used with "
+                        "atomic instructions"));
+}
+
+TEST_F(ValidateBarriers, CooperativeMatrixSpecConstantVolatile) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability CooperativeMatrixNV
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpSpecConstant %int 32768
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpControlBarrier %device %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateBarriers, CooperativeMatrixNonConstantSemantics) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability CooperativeMatrixNV
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%semantics = OpUndef %int
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpControlBarrier %device %device %semantics
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory Semantics must be a constant instruction when "
+                        "CooperativeMatrixNV capability is present"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index ec07582..e3eca09 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -24,7 +24,9 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "source/spirv_target_env.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
@@ -50,183 +52,64 @@
 using ::testing::ValuesIn;
 
 using ValidateBuiltIns = spvtest::ValidateBase<bool>;
+using ValidateVulkanSubgroupBuiltIns = spvtest::ValidateBase<
+    std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
                                      const char*, TestResult>>;
+using ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult =
+    spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
+                                     const char*, TestResult>>;
 using ValidateVulkanCombineBuiltInArrayedVariable = spvtest::ValidateBase<
     std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
+using ValidateWebGPUCombineBuiltInArrayedVariable = spvtest::ValidateBase<
+    std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
+using ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult =
+    spvtest::ValidateBase<
+        std::tuple<const char*, const char*, const char*, const char*,
+                   const char*, const char*, TestResult>>;
 
-struct EntryPoint {
-  std::string name;
-  std::string execution_model;
-  std::string execution_modes;
-  std::string body;
-  std::string interfaces;
-};
+bool InitializerRequired(spv_target_env env, const char* const storage_class) {
+  return spvIsWebGPUEnv(env) && (strncmp(storage_class, "Output", 6) == 0 ||
+                                 strncmp(storage_class, "Private", 7) == 0 ||
+                                 strncmp(storage_class, "Function", 8) == 0);
+}
 
-class CodeGenerator {
- public:
-  std::string Build() const;
+CodeGenerator GetInMainCodeGenerator(spv_target_env env,
+                                     const char* const built_in,
+                                     const char* const execution_model,
+                                     const char* const storage_class,
+                                     const char* const capabilities,
+                                     const char* const extensions,
+                                     const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
 
-  std::vector<EntryPoint> entry_points_;
-  std::string capabilities_;
-  std::string extensions_;
-  std::string memory_model_;
-  std::string before_types_;
-  std::string types_;
-  std::string after_types_;
-  std::string add_at_the_end_;
-};
-
-std::string CodeGenerator::Build() const {
-  std::ostringstream ss;
-
-  ss << capabilities_;
-  ss << extensions_;
-  ss << memory_model_;
-
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << "OpEntryPoint " << entry_point.execution_model << " %"
-       << entry_point.name << " \"" << entry_point.name << "\" "
-       << entry_point.interfaces << "\n";
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
   }
 
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << entry_point.execution_modes << "\n";
-  }
-
-  ss << before_types_;
-  ss << types_;
-  ss << after_types_;
-
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << "\n";
-    ss << "%" << entry_point.name << " = OpFunction %void None %func\n";
-    ss << "%" << entry_point.name << "_entry = OpLabel\n";
-    ss << entry_point.body;
-    ss << "\nOpReturn\nOpFunctionEnd\n";
-  }
-
-  ss << add_at_the_end_;
-
-  return ss.str();
-}
-
-std::string GetDefaultShaderCapabilities() {
-  return R"(
-OpCapability Shader
-OpCapability Geometry
-OpCapability Tessellation
-OpCapability Float64
-OpCapability Int64
-OpCapability MultiViewport
-OpCapability SampleRateShading
-)";
-}
-
-std::string GetDefaultShaderTypes() {
-  return R"(
-%void = OpTypeVoid
-%func = OpTypeFunction %void
-%bool = OpTypeBool
-%f32 = OpTypeFloat 32
-%f64 = OpTypeFloat 64
-%u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
-%f32vec2 = OpTypeVector %f32 2
-%f32vec3 = OpTypeVector %f32 3
-%f32vec4 = OpTypeVector %f32 4
-%f64vec2 = OpTypeVector %f64 2
-%f64vec3 = OpTypeVector %f64 3
-%f64vec4 = OpTypeVector %f64 4
-%u32vec2 = OpTypeVector %u32 2
-%u32vec3 = OpTypeVector %u32 3
-%u64vec3 = OpTypeVector %u64 3
-%u32vec4 = OpTypeVector %u32 4
-%u64vec2 = OpTypeVector %u64 2
-
-%f32_0 = OpConstant %f32 0
-%f32_1 = OpConstant %f32 1
-%f32_2 = OpConstant %f32 2
-%f32_3 = OpConstant %f32 3
-%f32_4 = OpConstant %f32 4
-%f32_h = OpConstant %f32 0.5
-%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
-%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
-%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
-%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
-%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
-%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
-
-%f64_0 = OpConstant %f64 0
-%f64_1 = OpConstant %f64 1
-%f64_2 = OpConstant %f64 2
-%f64_3 = OpConstant %f64 3
-%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
-%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
-%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
-
-%u32_0 = OpConstant %u32 0
-%u32_1 = OpConstant %u32 1
-%u32_2 = OpConstant %u32 2
-%u32_3 = OpConstant %u32 3
-%u32_4 = OpConstant %u32 4
-
-%u64_0 = OpConstant %u64 0
-%u64_1 = OpConstant %u64 1
-%u64_2 = OpConstant %u64 2
-%u64_3 = OpConstant %u64 3
-
-%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
-%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
-%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
-%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
-
-%u32arr2 = OpTypeArray %u32 %u32_2
-%u32arr3 = OpTypeArray %u32 %u32_3
-%u32arr4 = OpTypeArray %u32 %u32_4
-%u64arr2 = OpTypeArray %u64 %u32_2
-%u64arr3 = OpTypeArray %u64 %u32_3
-%u64arr4 = OpTypeArray %u64 %u32_4
-%f32arr2 = OpTypeArray %f32 %u32_2
-%f32arr3 = OpTypeArray %f32 %u32_3
-%f32arr4 = OpTypeArray %f32 %u32_4
-%f64arr2 = OpTypeArray %f64 %u32_2
-%f64arr3 = OpTypeArray %f64 %u32_3
-%f64arr4 = OpTypeArray %f64 %u32_4
-
-%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
-%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
-%f64vec4arr3 = OpTypeArray %f64vec4 %u32_3
-)";
-}
-
-CodeGenerator GetDefaultShaderCodeGenerator() {
-  CodeGenerator generator;
-  generator.capabilities_ = GetDefaultShaderCapabilities();
-  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
-  generator.types_ = GetDefaultShaderTypes();
-  return generator;
-}
-
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) {
-  const char* const built_in = std::get<0>(GetParam());
-  const char* const execution_model = std::get<1>(GetParam());
-  const char* const storage_class = std::get<2>(GetParam());
-  const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
-
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
+
   after_types << "%built_in_type = OpTypeStruct " << data_type << "\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull %built_in_type\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_type\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   after_types << "%data_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
   generator.after_types_ = after_types.str();
@@ -265,6 +148,20 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator =
+      GetInMainCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                             storage_class, NULL, NULL, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -276,24 +173,88 @@
   }
 }
 
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) {
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InMain) {
   const char* const built_in = std::get<0>(GetParam());
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator =
+      GetInMainCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                             storage_class, NULL, NULL, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    InMain) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetInMainCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+CodeGenerator GetInFunctionCodeGenerator(spv_target_env env,
+                                         const char* const built_in,
+                                         const char* const execution_model,
+                                         const char* const storage_class,
+                                         const char* const capabilities,
+                                         const char* const extensions,
+                                         const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
+  }
+
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
   after_types << "%built_in_type = OpTypeStruct " << data_type << "\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull %built_in_type\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_type\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   after_types << "%data_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
   generator.after_types_ = after_types.str();
@@ -331,15 +292,36 @@
 %val2 = OpFunctionCall %void %foo
 )";
 
-  generator.add_at_the_end_ = R"(
+  std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %ptr = OpAccessChain %data_ptr %built_in_var %u32_0
 OpReturn
 OpFunctionEnd
 )";
+
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator =
+      GetInFunctionCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                                 storage_class, NULL, NULL, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -351,23 +333,87 @@
   }
 }
 
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) {
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InFunction) {
   const char* const built_in = std::get<0>(GetParam());
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator =
+      GetInFunctionCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                                 storage_class, NULL, NULL, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    InFunction) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetInFunctionCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+CodeGenerator GetVariableCodeGenerator(spv_target_env env,
+                                       const char* const built_in,
+                                       const char* const execution_model,
+                                       const char* const storage_class,
+                                       const char* const capabilities,
+                                       const char* const extensions,
+                                       const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
+  if (capabilities) {
+    generator.capabilities_ += capabilities;
+  }
+  if (extensions) {
+    generator.extensions_ += extensions;
+  }
+
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull " << data_type << "\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   generator.after_types_ = after_types.str();
 
   EntryPoint entry_point;
@@ -379,7 +425,7 @@
   }
   // Any kind of reference would do.
   entry_point.body = R"(
-%val = OpBitcast %u64 %built_in_var
+%val = OpBitcast %u32 %built_in_var
 )";
 
   std::ostringstream execution_modes;
@@ -405,6 +451,20 @@
 
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator =
+      GetVariableCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
+                               storage_class, NULL, NULL, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -416,25 +476,73 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator =
+      GetVariableCodeGenerator(SPV_ENV_WEBGPU_0, built_in, execution_model,
+                               storage_class, NULL, NULL, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+TEST_P(
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const char* const capabilities = std::get<4>(GetParam());
+  const char* const extensions = std::get<5>(GetParam());
+  const TestResult& test_result = std::get<6>(GetParam());
+
+  CodeGenerator generator = GetVariableCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
+      capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Output"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Fragment", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Input"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceFragmentOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
@@ -444,9 +552,9 @@
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
                 "to be used for variables with Output storage class if "
                 "execution model is Fragment.",
-                "which is called with execution model Fragment."))), );
+                "which is called with execution model Fragment."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VertexIdAndInstanceIdVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexId", "InstanceId"), Values("Vertex"), Values("Input"),
@@ -454,9 +562,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn VertexId/InstanceId to be "
-                "used."))), );
+                "used."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Vertex"),
@@ -466,9 +574,9 @@
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("GLCompute"),
@@ -476,41 +584,46 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32vec2", "%f32vec4", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%u32arr2", "%u64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotF32Array,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f64arr2", "%f64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FragCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec4"), Values(TestResult())), );
+            Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32vec4"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -519,50 +632,91 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec4"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FragCoord"), Values("Vertex", "GLCompute"), Values("Input"),
+        Values("%f32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Output"),
             Values("%f32vec4"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotInput, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Output"),
+            Values("%f32vec4"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFloatVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32arr4", "%u32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "is not a float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFloatVec4,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "has 3 components"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FragDepthSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
-            Values("%f32"), Values(TestResult())), );
+            Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -571,9 +725,18 @@
                "TessellationEvaluation"),
         Values("Output"), Values("%f32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FragDepth"), Values("Vertex", "GLCompute"), Values("Output"),
+        Values("%f32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
@@ -581,32 +744,57 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Output storage class",
-                "uses storage class Input"))), );
+                "uses storage class Input"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotOutput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
+            Values("%f32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Output storage class",
+                "uses storage class Input"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFloatScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f32vec4", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFloatScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32vec4", "%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit float scalar",
+                              "is not a float scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
-            Values("Input"), Values("%bool"), Values(TestResult())), );
+            Values("Input"), Values("%bool"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Input"),
+            Values("%bool"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -615,9 +803,18 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%bool"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FrontFacing"), Values("Vertex", "GLCompute"), Values("Input"),
+        Values("%bool"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
@@ -625,38 +822,73 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Output"),
+            Values("%bool"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotBool,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
             Values("Input"), Values("%f32", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a bool scalar",
-                              "is not a bool scalar"))), );
+                              "is not a bool scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotBool,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Input"),
+            Values("%f32", "%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a bool scalar",
+                              "is not a bool scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3Success,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"), Values("%u32vec3"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3Success,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"), Values("%u32vec3"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotGLCompute,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
-                   "WorkgroupId"),
-            Values("Vertex", "Fragment", "Geometry", "TessellationControl",
-                   "TessellationEvaluation"),
-            Values("Input"), Values("%u32vec3"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be used only with GLCompute execution model"))), );
+    Combine(
+        Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
+               "WorkgroupId"),
+        Values("Vertex", "Fragment", "Geometry", "TessellationControl",
+               "TessellationEvaluation"),
+        Values("Input"), Values("%u32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotGLCompute,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+        Values("Vertex", "Fragment"), Values("Input"), Values("%u32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -665,9 +897,19 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Output"), Values("%u32vec3"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotIntVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -676,9 +918,19 @@
             Values("%u32arr3", "%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "is not an int vector"))), );
+                              "is not an int vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotIntVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"),
+            Values("%u32arr3", "%f32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 3-component 32-bit int vector",
+                              "is not an int vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotIntVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -686,9 +938,18 @@
             Values("GLCompute"), Values("Input"), Values("%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "has 4 components"))), );
+                              "has 4 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotIntVec3,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"), Values("%u32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 3-component 32-bit int vector",
+                              "has 4 components"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInt32Vec,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -696,15 +957,15 @@
             Values("GLCompute"), Values("Input"), Values("%u64vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"),
@@ -712,9 +973,9 @@
             Values("Input"), Values("%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "Geometry execution models"))), );
+                              "Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
@@ -722,44 +983,57 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(
-        Values("InstanceIndex"),
-        Values("Geometry", "Fragment", "GLCompute", "TessellationControl",
-               "TessellationEvaluation"),
-        Values("Input"), Values("%u32"),
-        Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Vertex execution model"))), );
+    Combine(Values("InstanceIndex"),
+            Values("Geometry", "Fragment", "GLCompute", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Fragment", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"),
@@ -767,39 +1041,58 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"),
+            Values("%u32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Geometry"),
-            Values("Output"), Values("%u32"), Values(TestResult())), );
+            Values("Output"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"),
@@ -808,9 +1101,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationEvaluation, "
-                "Geometry, or Fragment execution models"))), );
+                "Geometry, or Fragment execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexExecutionModelEnabledByCapability,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"),
@@ -818,9 +1111,9 @@
             Values("%u32"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
-                "requires the ShaderViewportIndexLayerEXT capability"))), );
+                "requires the ShaderViewportIndexLayerEXT capability"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexFragmentNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -828,9 +1121,9 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
-                          "which is called with execution model Fragment"))), );
+                          "which is called with execution model Fragment"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexGeometryNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -840,34 +1133,34 @@
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Input storage class if execution model is Vertex, "
                           "TessellationEvaluation, or Geometry",
-                          "which is called with execution model"))), );
+                          "which is called with execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -875,9 +1168,9 @@
             Values("Input"), Values("%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models"))), );
+                              "TessellationEvaluation execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -886,9 +1179,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -896,9 +1189,9 @@
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -906,14 +1199,14 @@
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())), );
+            Values("%f32vec2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -922,9 +1215,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Output"),
@@ -932,51 +1225,51 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32"), Values(TestResult())), );
+            Values("Output"), Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32"), Values(TestResult())), );
+            Values("Input"), Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Input"),
@@ -986,9 +1279,9 @@
                 "Vulkan spec doesn't allow BuiltIn PointSize "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("GLCompute", "Fragment"),
@@ -996,41 +1289,66 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeNotFloatScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f32vec4", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32vec4"), Values(TestResult())), );
+            Values("Output"), Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionOutputSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("Position"), Values("Vertex"), Values("Output"),
+            Values("%f32vec4"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    PositionOutputFailure,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("Position"), Values("Fragment", "GLCompute"),
+            Values("Output"), Values("%f32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "WebGPU spec allows BuiltIn Position to be used "
+                              "only with the Vertex execution model."))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32vec4"), Values(TestResult())), );
+            Values("Input"), Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionInputFailure,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex", "Fragment", "GLCompute"),
+        Values("Input"), Values("%f32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn Position to be only used "
+                          "for variables with Output storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Vertex"), Values("Input"),
@@ -1040,9 +1358,9 @@
                 "Vulkan spec doesn't allow BuiltIn Position "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("GLCompute", "Fragment"),
@@ -1050,50 +1368,68 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionNotFloatVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex"), Values("Output"),
+        Values("%f32arr4", "%u32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 4-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionNotFloatVec4,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex"), Values("Output"),
+        Values("%f32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 4-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f64vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"),
             Values("Fragment", "TessellationControl", "TessellationEvaluation",
                    "Geometry"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Geometry"), Values("Output"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"),
@@ -1101,9 +1437,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdFragmentNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1111,9 +1447,9 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
-                          "which is called with execution model Fragment"))), );
+                          "which is called with execution model Fragment"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdGeometryNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"),
@@ -1122,32 +1458,32 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Output storage class if execution model is Tessellation",
-                "which is called with execution model Tessellation"))), );
+                "which is called with execution model Tessellation"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1156,40 +1492,40 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("SampleId"), Values("Fragment"), Values("Output"),
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn SampleId to be only used "
-                          "for variables with Input storage class"))), );
+                          "for variables with Input storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input", "Output"),
-            Values("%u32arr2", "%u32arr4"), Values(TestResult())), );
+            Values("%u32arr2", "%u32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1198,9 +1534,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%u32arr2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskWrongStorageClass,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
@@ -1208,42 +1544,42 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec allows BuiltIn SampleMask to be only used for "
-                "variables with Input or Output storage class"))), );
+                "variables with Input or Output storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotIntArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%f32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "components are not int scalar"))), );
+                              "components are not int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotInt32Array,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%u64arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())), );
+            Values("%f32vec2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1252,9 +1588,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Output"),
@@ -1262,41 +1598,41 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFloatVec2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotF32Vec2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f64vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32vec3"), Values(TestResult())), );
+            Values("Input"), Values("%f32vec3"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1306,57 +1642,57 @@
         Values("Input"), Values("%f32vec3"),
         Values(TestResult(
             SPV_ERROR_INVALID_DATA,
-            "to be used only with TessellationEvaluation execution model"))), );
+            "to be used only with TessellationEvaluation execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Output"),
             Values("%f32vec3"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr3", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "has 2 components"))), );
+                              "has 2 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotF32Vec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr4"), Values(TestResult())), );
+            Values("Input"), Values("%f32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr4"), Values(TestResult())), );
+            Values("Output"), Values("%f32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"),
@@ -1364,9 +1700,9 @@
             Values("Input"), Values("%f32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models."))), );
+                              "TessellationEvaluation execution models."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
@@ -1375,9 +1711,9 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Output storage class if execution "
-                "model is TessellationEvaluation."))), );
+                "model is TessellationEvaluation."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
@@ -1386,57 +1722,57 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Input storage class if execution "
-                "model is TessellationControl."))), );
+                "model is TessellationControl."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec4", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotFloatArr4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotF32Arr4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr2"), Values(TestResult())), );
+            Values("Input"), Values("%f32arr2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr2"), Values(TestResult())), );
+            Values("Output"), Values("%f32arr2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"),
@@ -1444,9 +1780,9 @@
             Values("Input"), Values("%f32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models."))), );
+                              "TessellationEvaluation execution models."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
@@ -1455,9 +1791,9 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Output storage class if execution "
-                "model is TessellationEvaluation."))), );
+                "model is TessellationEvaluation."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
@@ -1466,62 +1802,75 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Input storage class if execution "
-                "model is TessellationControl."))), );
+                "model is TessellationControl."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotFloatArr2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotF32Arr2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(
-        Values("VertexIndex"),
-        Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
-               "TessellationEvaluation"),
-        Values("Input"), Values("%u32"),
-        Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Vertex execution model"))), );
+    Combine(Values("VertexIndex"),
+            Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Fragment", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1529,44 +1878,115 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn VertexIndex to be only "
-                          "used for variables with Input storage class"))), );
+                          "used for variables with Input storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("VertexIndex"), Values("Vertex"), Values("Output"),
+        Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn VertexIndex to be only "
+                          "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-TEST_P(ValidateVulkanCombineBuiltInArrayedVariable, Variable) {
-  const char* const built_in = std::get<0>(GetParam());
-  const char* const execution_model = std::get<1>(GetParam());
-  const char* const storage_class = std::get<2>(GetParam());
-  const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("LocalInvocationIndex"), Values("GLCompute"),
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("LocalInvocationIndex"), Values("Fragment", "Vertex"),
+        Values("Input"), Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("LocalInvocationIndex"), Values("GLCompute"), Values("Output"),
+        Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn LocalInvocationIndex to "
+                          "be only used for variables with Input storage "
+                          "class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("LocalInvocationIndex"), Values("GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int", "is not an int"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    WhitelistRejection,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("PointSize", "ClipDistance", "CullDistance", "VertexId",
+                   "InstanceId", "PointCoord", "SampleMask", "HelperInvocation",
+                   "WorkgroupId"),
+            Values("Vertex"), Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "WebGPU does not allow BuiltIn"))));
+
+CodeGenerator GetArrayedVariableCodeGenerator(spv_target_env env,
+                                              const char* const built_in,
+                                              const char* const execution_model,
+                                              const char* const storage_class,
+                                              const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
   after_types << "%built_in_array = OpTypeArray " << data_type << " %u32_3\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_array_null = OpConstantNull %built_in_array\n";
+  }
+
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_array\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_array_null";
+  }
+  after_types << "\n";
   generator.after_types_ = after_types.str();
 
   EntryPoint entry_point;
@@ -1575,7 +1995,7 @@
   entry_point.interfaces = "%built_in_var";
   // Any kind of reference would do.
   entry_point.body = R"(
-%val = OpBitcast %u64 %built_in_var
+%val = OpBitcast %u32 %built_in_var
 )";
 
   std::ostringstream execution_modes;
@@ -1601,6 +2021,19 @@
 
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInArrayedVariable, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetArrayedVariableCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -1612,78 +2045,185 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(PointSizeArrayedF32TessControl,
-                        ValidateVulkanCombineBuiltInArrayedVariable,
-                        Combine(Values("PointSize"),
-                                Values("TessellationControl"), Values("Input"),
-                                Values("%f32"), Values(TestResult())), );
+TEST_P(ValidateWebGPUCombineBuiltInArrayedVariable, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
 
-INSTANTIATE_TEST_CASE_P(
+  CodeGenerator generator = GetArrayedVariableCodeGenerator(
+      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(PointSizeArrayedF32TessControl,
+                         ValidateVulkanCombineBuiltInArrayedVariable,
+                         Combine(Values("PointSize"),
+                                 Values("TessellationControl"), Values("Input"),
+                                 Values("%f32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF64TessControl, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("TessellationControl"), Values("Input"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF32Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(PositionArrayedF32Vec4TessControl,
-                        ValidateVulkanCombineBuiltInArrayedVariable,
-                        Combine(Values("Position"),
-                                Values("TessellationControl"), Values("Input"),
-                                Values("%f32vec4"), Values(TestResult())), );
+INSTANTIATE_TEST_SUITE_P(PositionArrayedF32Vec4TessControl,
+                         ValidateVulkanCombineBuiltInArrayedVariable,
+                         Combine(Values("Position"),
+                                 Values("TessellationControl"), Values("Input"),
+                                 Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec3TessControl,
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("TessellationControl"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec4Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("Vertex"), Values("Output"),
-            Values("%f32"),
+            Values("%f32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionArrayedF32Vec4Vertex, ValidateWebGPUCombineBuiltInArrayedVariable,
+    Combine(Values("Position"), Values("Vertex"), Values("Output"),
+            Values("%f32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "is not a float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceOutputSuccess,
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
             Values("Output"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceVertexInput, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotArray, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputMeshSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+        Values("MeshNV", "TaskNV"), Values("Input"), Values("%u32"),
+        Values("OpCapability ShaderSMBuiltinsNV\nOpCapability MeshShadingNV\n"),
+        Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
+               "\"SPV_NV_mesh_shader\"\n"),
+        Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsInputRaySuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+        Values("RayGenerationNV", "IntersectionNV", "AnyHitNV", "ClosestHitNV",
+               "MissNV", "CallableNV"),
+        Values("Input"), Values("%u32"),
+        Values("OpCapability ShaderSMBuiltinsNV\nOpCapability RayTracingNV\n"),
+        Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
+               "\"SPV_NV_ray_tracing\"\n"),
+        Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SMBuiltinsNotInt32,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("SMCountNV", "SMIDNV", "WarpsPerSMNV", "WarpIDNV"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u64"),
+            Values("OpCapability ShaderSMBuiltinsNV\n"),
+            Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "has bit width 64"))));
+
+CodeGenerator GetWorkgroupSizeSuccessGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1700,12 +2240,27 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeSuccess) {
+  CodeGenerator generator =
+      GetWorkgroupSizeSuccessGenerator(SPV_ENV_VULKAN_1_0);
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeFragment) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeSuccess) {
+  CodeGenerator generator = GetWorkgroupSizeSuccessGenerator(SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+CodeGenerator GetWorkgroupSizeFragmentGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1723,6 +2278,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeFragment) {
+  CodeGenerator generator =
+      GetWorkgroupSizeFragmentGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1734,8 +2296,22 @@
                         "called with execution model Fragment"));
 }
 
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeFragment) {
+  CodeGenerator generator = GetWorkgroupSizeFragmentGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec allows BuiltIn WorkgroupSize to be used "
+                        "only with GLCompute execution model"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("is referencing ID <2> (OpConstantComposite) which is "
+                        "decorated with BuiltIn WorkgroupSize in function <1> "
+                        "called with execution model Fragment"));
+}
+
 TEST_F(ValidateBuiltIns, WorkgroupSizeNotConstant) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %copy BuiltIn WorkgroupSize
 )";
@@ -1759,8 +2335,11 @@
                         "constant. ID <2> (OpCopyObject) is not a constant"));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotVector) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+CodeGenerator GetWorkgroupSizeNotVectorGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1777,6 +2356,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVectorGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1785,8 +2371,23 @@
                         "ID <2> (OpConstant) is not an int vector."));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotIntVector) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVectorGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstant) is not an int vector."));
+}
+
+CodeGenerator GetWorkgroupSizeNotIntVectorGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1803,6 +2404,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotIntVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotIntVectorGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1811,8 +2419,23 @@
                         "ID <2> (OpConstantComposite) is not an int vector."));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotVec3) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotIntVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotIntVectorGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstantComposite) is not an int vector."));
+}
+
+CodeGenerator GetWorkgroupSizeNotVec3Generator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1829,6 +2452,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVec3) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVec3Generator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1837,8 +2467,19 @@
                         "ID <2> (OpConstantComposite) has 2 components."));
 }
 
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVec3) {
+  CodeGenerator generator = GetWorkgroupSizeNotVec3Generator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstantComposite) has 2 components."));
+}
+
 TEST_F(ValidateBuiltIns, WorkgroupSizeNotInt32Vec) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1865,7 +2506,7 @@
 }
 
 TEST_F(ValidateBuiltIns, WorkgroupSizePrivateVar) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1888,7 +2529,7 @@
 }
 
 TEST_F(ValidateBuiltIns, GeometryPositionInOutSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn Position
@@ -1927,7 +2568,7 @@
 }
 
 TEST_F(ValidateBuiltIns, WorkgroupIdNotVec3) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 OpDecorate %workgroup_id BuiltIn WorkgroupId
@@ -1958,7 +2599,7 @@
 }
 
 TEST_F(ValidateBuiltIns, TwoBuiltInsFirstFails) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn FragCoord
@@ -1998,7 +2639,7 @@
 }
 
 TEST_F(ValidateBuiltIns, TwoBuiltInsSecondFails) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn Position
@@ -2038,7 +2679,7 @@
 }
 
 TEST_F(ValidateBuiltIns, VertexPositionVariableSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %position BuiltIn Position
 )";
@@ -2062,7 +2703,7 @@
 }
 
 TEST_F(ValidateBuiltIns, FragmentPositionTwoEntryPoints) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn Position
 )";
@@ -2111,16 +2752,20 @@
               HasSubstr("called with execution model Fragment"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthNoDepthReplacing) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+CodeGenerator GetNoDepthReplacingGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2134,7 +2779,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2143,6 +2788,18 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2150,16 +2807,31 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
+CodeGenerator GetOneMainHasDepthReplacingOtherHasntGenerator(
+    spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2184,7 +2856,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2193,6 +2865,20 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns,
+       VulkanFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2200,8 +2886,20 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
+TEST_F(ValidateBuiltIns,
+       WebGPUFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
 TEST_F(ValidateBuiltIns, AllowInstanceIdWithIntersectionShader) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(
 OpCapability RayTracingNV
 )";
@@ -2241,7 +2939,7 @@
 }
 
 TEST_F(ValidateBuiltIns, DisallowInstanceIdWithRayGenShader) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(
 OpCapability RayTracingNV
 )";
@@ -2278,6 +2976,332 @@
                         "AnyHitNV execution models"));
 }
 
+TEST_F(ValidateBuiltIns, ValidBuiltinsForMeshShader) {
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+  generator.capabilities_ += R"(
+OpCapability MeshShadingNV 
+)";
+
+  generator.extensions_ = R"(
+OpExtension "SPV_NV_mesh_shader"
+)";
+
+  generator.before_types_ = R"(
+OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+OpDecorate %gl_PrimitiveID PerPrimitiveNV
+OpDecorate %gl_Layer BuiltIn Layer
+OpDecorate %gl_Layer PerPrimitiveNV
+OpDecorate %gl_ViewportIndex BuiltIn ViewportIndex
+OpDecorate %gl_ViewportIndex PerPrimitiveNV
+)";
+
+  generator.after_types_ = R"(
+%u32_81 = OpConstant %u32 81
+%_arr_int_uint_81 = OpTypeArray %i32 %u32_81
+%_ptr_Output__arr_int_uint_81 = OpTypePointer Output %_arr_int_uint_81
+%gl_PrimitiveID = OpVariable %_ptr_Output__arr_int_uint_81 Output
+%gl_Layer = OpVariable %_ptr_Output__arr_int_uint_81 Output
+%gl_ViewportIndex = OpVariable %_ptr_Output__arr_int_uint_81 Output
+)";
+
+  EntryPoint entry_point;
+  entry_point.name = "main_d_r";
+  entry_point.execution_model = "MeshNV";
+  entry_point.interfaces = "%gl_PrimitiveID %gl_Layer %gl_ViewportIndex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateBuiltIns, InvalidBuiltinsForMeshShader) {
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+  generator.capabilities_ += R"(
+OpCapability MeshShadingNV 
+)";
+
+  generator.extensions_ = R"(
+OpExtension "SPV_NV_mesh_shader"
+)";
+
+  generator.before_types_ = R"(
+OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+OpDecorate %gl_PrimitiveID PerPrimitiveNV
+OpDecorate %gl_Layer BuiltIn Layer
+OpDecorate %gl_Layer PerPrimitiveNV
+OpDecorate %gl_ViewportIndex BuiltIn ViewportIndex
+OpDecorate %gl_ViewportIndex PerPrimitiveNV
+)";
+
+  generator.after_types_ = R"(
+%u32_81 = OpConstant %u32 81
+%_arr_float_uint_81 = OpTypeArray %f32 %u32_81
+%_ptr_Output__arr_float_uint_81 = OpTypePointer Output %_arr_float_uint_81
+%gl_PrimitiveID = OpVariable %_ptr_Output__arr_float_uint_81 Output
+%gl_Layer = OpVariable %_ptr_Output__arr_float_uint_81 Output
+%gl_ViewportIndex = OpVariable %_ptr_Output__arr_float_uint_81 Output
+)";
+
+  EntryPoint entry_point;
+  entry_point.name = "main_d_r";
+  entry_point.execution_model = "MeshNV";
+  entry_point.interfaces = "%gl_PrimitiveID %gl_Layer %gl_ViewportIndex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("needs to be a 32-bit int scalar"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("is not an int scalar"));
+}
+
+TEST_F(ValidateBuiltIns, GetUnderlyingTypeNoAssert) {
+  std::string spirv = R"(
+                      OpCapability Shader
+                      OpMemoryModel Logical GLSL450
+                      OpEntryPoint Fragment %4 "PSMa" %12 %17
+                      OpExecutionMode %4 OriginUpperLeft
+                      OpDecorate %gl_PointCoord BuiltIn PointCoord
+              %void = OpTypeVoid
+                 %3 = OpTypeFunction %void
+             %float = OpTypeFloat 32
+           %v4float = OpTypeVector %float 4
+       %gl_PointCoord = OpTypeStruct %v4float
+       %_ptr_Input_v4float = OpTypePointer Input %v4float
+       %_ptr_Output_v4float = OpTypePointer Output %v4float
+                %12 = OpVariable %_ptr_Input_v4float Input
+                %17 = OpVariable %_ptr_Output_v4float Output
+                 %4 = OpFunction %void None %3
+                %15 = OpLabel
+                      OpReturn
+                      OpFunctionEnd)";
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("did not find an member index to get underlying data "
+                        "type"));
+}
+
+TEST_P(ValidateVulkanSubgroupBuiltIns, InMain) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
+  generator.capabilities_ += R"(
+OpCapability GroupNonUniformBallot
+)";
+
+  generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
+  generator.before_types_ += built_in;
+  generator.before_types_ += "\n";
+
+  std::ostringstream after_types;
+  after_types << "%built_in_ptr = OpTypePointer " << storage_class << " "
+              << data_type << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  after_types << "\n";
+  generator.after_types_ = after_types.str();
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = execution_model;
+  if (strncmp(storage_class, "Input", 5) == 0 ||
+      strncmp(storage_class, "Output", 6) == 0) {
+    entry_point.interfaces = "%built_in_var";
+  }
+  entry_point.body =
+      std::string("%ld = OpLoad ") + data_type + " %built_in_var\n";
+
+  std::ostringstream execution_modes;
+  if (0 == std::strcmp(execution_model, "Fragment")) {
+    execution_modes << "OpExecutionMode %" << entry_point.name
+                    << " OriginUpperLeft\n";
+    if (0 == std::strcmp(built_in, "FragDepth")) {
+      execution_modes << "OpExecutionMode %" << entry_point.name
+                      << " DepthReplacing\n";
+    }
+  }
+  if (0 == std::strcmp(execution_model, "Geometry")) {
+    execution_modes << "OpExecutionMode %" << entry_point.name
+                    << " InputPoints\n";
+    execution_modes << "OpExecutionMode %" << entry_point.name
+                    << " OutputPoints\n";
+  }
+  if (0 == std::strcmp(execution_model, "GLCompute")) {
+    execution_modes << "OpExecutionMode %" << entry_point.name
+                    << " LocalSize 1 1 1\n";
+  }
+  entry_point.execution_modes = execution_modes.str();
+
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupMaskNotVec4, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupEqMask", "SubgroupGeMask", "SubgroupGtMask",
+                   "SubgroupLeMask", "SubgroupLtMask"),
+            Values("GLCompute"), Values("Input"), Values("%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit int vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupMaskNotU32, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupEqMask", "SubgroupGeMask", "SubgroupGtMask",
+                   "SubgroupLeMask", "SubgroupLtMask"),
+            Values("GLCompute"), Values("Input"), Values("%f32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit int vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupMaskNotInput, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupEqMask", "SubgroupGeMask", "SubgroupGtMask",
+                   "SubgroupLeMask", "SubgroupLtMask"),
+            Values("GLCompute"), Values("Output", "Workgroup", "Private"),
+            Values("%u32vec4"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(SubgroupMaskOk, ValidateVulkanSubgroupBuiltIns,
+                         Combine(Values("SubgroupEqMask", "SubgroupGeMask",
+                                        "SubgroupGtMask", "SubgroupLeMask",
+                                        "SubgroupLtMask"),
+                                 Values("GLCompute"), Values("Input"),
+                                 Values("%u32vec4"),
+                                 Values(TestResult(SPV_SUCCESS, ""))));
+
+TEST_F(ValidateBuiltIns, SubgroupMaskMemberDecorate) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniformBallot
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+OpMemberDecorate %struct 0 BuiltIn SubgroupEqMask
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "BuiltIn SubgroupEqMask cannot be used as a member decoration"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupInvocationIdAndSizeNotU32, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupLocalInvocationId", "SubgroupSize"),
+            Values("GLCompute"), Values("Input"), Values("%f32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupInvocationIdAndSizeNotInput, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupLocalInvocationId", "SubgroupSize"),
+            Values("GLCompute"), Values("Output", "Workgroup", "Private"),
+            Values("%u32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupInvocationIdAndSizeOk, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupLocalInvocationId", "SubgroupSize"),
+            Values("GLCompute"), Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_SUCCESS, ""))));
+
+TEST_F(ValidateBuiltIns, SubgroupSizeMemberDecorate) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniform
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+OpMemberDecorate %struct 0 BuiltIn SubgroupSize
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("BuiltIn SubgroupSize cannot be used as a member decoration"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupNumAndIdNotU32, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupId", "NumSubgroups"), Values("GLCompute"),
+            Values("Input"), Values("%f32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    SubgroupNumAndIdNotInput, ValidateVulkanSubgroupBuiltIns,
+    Combine(Values("SubgroupId", "NumSubgroups"), Values("GLCompute"),
+            Values("Output", "Workgroup", "Private"), Values("%u32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(SubgroupNumAndIdOk, ValidateVulkanSubgroupBuiltIns,
+                         Combine(Values("SubgroupId", "NumSubgroups"),
+                                 Values("GLCompute"), Values("Input"),
+                                 Values("%u32"),
+                                 Values(TestResult(SPV_SUCCESS, ""))));
+
+TEST_F(ValidateBuiltIns, SubgroupIdMemberDecorate) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability GroupNonUniform
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+OpMemberDecorate %struct 0 BuiltIn SubgroupId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("BuiltIn SubgroupId cannot be used as a member decoration"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 488e957..5a6e751 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -611,7 +611,7 @@
   "           OpReturn"
   "           OpFunctionEnd ";
 
-INSTANTIATE_TEST_CASE_P(ExecutionModel, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(ExecutionModel, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -639,9 +639,9 @@
 std::make_pair(std::string(kGLSL450MemoryModel) +
           " OpEntryPoint Kernel %func \"shader\"" +
           std::string(kVoidFVoid), KernelDependencies())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(AddressingAndMemoryModel, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(AddressingAndMemoryModel, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -681,9 +681,9 @@
           " OpMemoryModel Physical64 OpenCL"
           " OpEntryPoint Kernel %func \"compute\"" +
           std::string(kVoidFVoid),  AddressesDependencies())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(ExecutionMode, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(ExecutionMode, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -836,11 +836,11 @@
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Kernel %func \"shader\" "
           "OpExecutionMode %func ContractionOff" +
-          std::string(kVoidFVoid), KernelDependencies()))),);
+          std::string(kVoidFVoid), KernelDependencies()))));
 
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModeV11, ValidateCapabilityV11,
     Combine(ValuesIn(AllCapabilities()),
             Values(std::make_pair(std::string(kOpenCLMemoryModel) +
@@ -853,10 +853,10 @@
                            "OpEntryPoint Kernel %func \"shader\" "
                            "OpExecutionMode %func SubgroupsPerWorkgroup 65535" +
                            std::string(kVoidFVoid),
-                       std::vector<std::string>{"SubgroupDispatch"}))), );
+                       std::vector<std::string>{"SubgroupDispatch"}))));
 // clang-format off
 
-INSTANTIATE_TEST_CASE_P(StorageClass, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -920,9 +920,9 @@
           " %ptrt = OpTypePointer Image %intt\n"
           " %var = OpVariable %ptrt Image\n" + std::string(kVoidFVoid),
           AllCapabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Dim, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(Dim, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -968,11 +968,11 @@
           " %voidt = OpTypeVoid"
           " %imgt = OpTypeImage %voidt SubpassData 0 0 0 2 Unknown" + std::string(kVoidFVoid2),
           std::vector<std::string>{"InputAttachment"})
-)),);
+)));
 
 // NOTE: All Sampler Address Modes require kernel capabilities but the
 // OpConstantSampler requires LiteralSampler which depends on Kernel
-INSTANTIATE_TEST_CASE_P(SamplerAddressingMode, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(SamplerAddressingMode, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1006,7 +1006,7 @@
           " %sampler = OpConstantSampler %samplert RepeatMirrored 1 Nearest" +
           std::string(kVoidFVoid),
           std::vector<std::string>{"LiteralSampler"})
-)),);
+)));
 
 // TODO(umar): Sampler Filter Mode
 // TODO(umar): Image Format
@@ -1019,7 +1019,7 @@
 // TODO(umar): Access Qualifier
 // TODO(umar): Function Parameter Attribute
 
-INSTANTIATE_TEST_CASE_P(Decoration, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(Decoration, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1129,9 +1129,14 @@
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
+          // NonWritable must target something valid, such as a storage image.
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt NonWritable\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var NonWritable "
+          "%float = OpTypeFloat 32 "
+          "%imstor = OpTypeImage %float 2D 0 0 0 2 Unknown "
+          "%ptr = OpTypePointer UniformConstant %imstor "
+          "%var = OpVariable %ptr UniformConstant "
+          + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
@@ -1163,8 +1168,10 @@
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Component 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Component 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
@@ -1226,10 +1233,10 @@
           "OpDecorate %intt Alignment 4\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           KernelDependencies())
-)),);
+)));
 
 // clang-format on
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationSpecId, ValidateCapability,
     Combine(
         ValuesIn(AllSpirV10Capabilities()),
@@ -1239,9 +1246,9 @@
                                   "%intt = OpTypeInt 32 0\n"
                                   "%1 = OpSpecConstant %intt 0\n" +
                                   std::string(kVoidFVoid),
-                              ShaderDependencies()))), );
+                              ShaderDependencies()))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationV11, ValidateCapabilityV11,
     Combine(ValuesIn(AllCapabilities()),
             Values(std::make_pair(std::string(kOpenCLMemoryModel) +
@@ -1270,10 +1277,10 @@
                                    "%intt = OpTypeInt 32 0 "
                                    "%1 = OpSpecConstant %intt 0") +
                            std::string(kVoidFVoid),
-                       ShaderDependencies()))), );
+                       ShaderDependencies()))));
 // clang-format off
 
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1495,13 +1502,13 @@
           "OpDecorate %intt BuiltIn InstanceIndex\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           ShaderDependencies())
-)),);
+)));
 
 // Ensure that mere mention of PointSize, ClipDistance, or CullDistance as
 // BuiltIns does not trigger the requirement for the associated
 // capability.
 // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityVulkan10,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityVulkan10,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllSpirV10Capabilities()),
@@ -1532,9 +1539,9 @@
           "%f32arr4 = OpTypeArray %f32 %intt_4\n"
           "%block = OpTypeStruct %f32arr4\n" + std::string(kVoidFVoid),
           AllVulkan10Capabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityOpenGL40,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityOpenGL40,
                         Combine(
                             // OpenGL 4.0 is based on SPIR-V 1.0
                             ValuesIn(AllSpirV10Capabilities()),
@@ -1554,9 +1561,9 @@
           "OpDecorate %intt BuiltIn CullDistance\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllSpirV10Capabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Capabilities, ValidateCapabilityWebGPU,
+INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityWebGPU,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllCapabilities()),
@@ -1564,9 +1571,9 @@
 std::make_pair(std::string(kVulkanMemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" + std::string(kVoidFVoid),
           AllWebGPUCapabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Capabilities, ValidateCapabilityVulkan11,
+INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityVulkan11,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllCapabilities()),
@@ -1581,7 +1588,7 @@
           "OpDecorate %intt BuiltIn CullDistance\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllVulkan11Capabilities())
-)),);
+)));
 
 // TODO(umar): Selection Control
 // TODO(umar): Loop Control
@@ -1593,7 +1600,7 @@
 // TODO(umar): Kernel Enqueue Flags
 // TODO(umar): Kernel Profiling Flags
 
-INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(MatrixOp, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1602,7 +1609,7 @@
           "%f32      = OpTypeFloat 32\n"
           "%vec3     = OpTypeVector %f32 3\n"
           "%mat33    = OpTypeMatrix %vec3 3\n" + std::string(kVoidFVoid),
-          MatrixDependencies()))),);
+          MatrixDependencies()))));
 // clang-format on
 
 #if 0
@@ -1643,7 +1650,7 @@
   return ss.str();
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TwoImageOperandsMask, ValidateCapability,
     Combine(
         ValuesIn(AllCapabilities()),
@@ -2260,8 +2267,8 @@
   EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("operand 5255 requires one of these extensions: "
-                        "SPV_NV_viewport_array2"));
+              HasSubstr("operand ShaderViewportMaskNV(5255) requires one of "
+                        "these extensions: SPV_NV_viewport_array2"));
 }
 
 TEST_F(ValidateCapability,
@@ -2342,8 +2349,8 @@
   EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("operand 5568 requires one of these extensions: "
-                        "SPV_INTEL_subgroups"));
+              HasSubstr("operand SubgroupShuffleINTEL(5568) requires one of "
+                        "these extensions: SPV_INTEL_subgroups"));
 }
 
 TEST_F(ValidateCapability,
@@ -2402,6 +2409,33 @@
                         "specified if the VulkanKHR memory model is used"));
 }
 
+// In the grammar, SubgroupEqMask and SubgroupMaskKHR have different enabling
+// lists of extensions.
+TEST_F(ValidateCapability, SubgroupEqMaskEnabledByExtension) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_KHR_shader_ballot"
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpDecorate %var BuiltIn SubgroupEqMask
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%ptr_uint = OpTypePointer Private %uint
+%var = OpVariable %ptr_uint Private
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+%val = OpLoad %uint %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_0))
+      << getDiagnosticString();
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index aed0a57..b22db06 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -25,6 +25,7 @@
 #include "gmock/gmock.h"
 
 #include "source/diagnostic.h"
+#include "source/spirv_target_env.h"
 #include "source/val/validate.h"
 #include "test/test_fixture.h"
 #include "test/unit_spirv.h"
@@ -103,6 +104,12 @@
         }
         out << ss.str();
       } break;
+      case SpvOpLoopMerge: {
+        assert(successors_.size() == 2);
+        out << "OpLoopMerge %" + successors_[0].label_ + " %" +
+                   successors_[0].label_ + "None";
+      } break;
+
       case SpvOpReturn:
         assert(successors_.size() == 0);
         out << "OpReturn\n";
@@ -115,6 +122,10 @@
         assert(successors_.size() == 1);
         out << "OpBranch %" + successors_.front().label_;
         break;
+      case SpvOpKill:
+        assert(successors_.size() == 0);
+        out << "OpKill\n";
+        break;
       default:
         assert(1 == 0 && "Unhandled");
     }
@@ -144,13 +155,13 @@
   return lhs;
 }
 
-const char* header(SpvCapability cap) {
-  static const char* shader_header =
+const std::string& GetDefaultHeader(SpvCapability cap) {
+  static const std::string shader_header =
       "OpCapability Shader\n"
       "OpCapability Linkage\n"
       "OpMemoryModel Logical GLSL450\n";
 
-  static const char* kernel_header =
+  static const std::string kernel_header =
       "OpCapability Kernel\n"
       "OpCapability Linkage\n"
       "OpMemoryModel Logical OpenCL\n";
@@ -158,8 +169,17 @@
   return (cap == SpvCapabilityShader) ? shader_header : kernel_header;
 }
 
-const char* types_consts() {
-  static const char* types =
+const std::string& GetWebGPUHeader() {
+  static const std::string header =
+      "OpCapability Shader\n"
+      "OpCapability VulkanMemoryModelKHR\n"
+      "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+      "OpMemoryModel Logical VulkanKHR\n";
+  return header;
+}
+
+const std::string& types_consts() {
+  static const std::string types =
       "%voidt   = OpTypeVoid\n"
       "%boolt   = OpTypeBool\n"
       "%intt    = OpTypeInt 32 0\n"
@@ -167,13 +187,12 @@
       "%two     = OpConstant %intt 2\n"
       "%ptrt    = OpTypePointer Function %intt\n"
       "%funct   = OpTypeFunction %voidt\n";
-
   return types;
 }
 
-INSTANTIATE_TEST_CASE_P(StructuredControlFlow, ValidateCFG,
-                        ::testing::Values(SpvCapabilityShader,
-                                          SpvCapabilityKernel));
+INSTANTIATE_TEST_SUITE_P(StructuredControlFlow, ValidateCFG,
+                         ::testing::Values(SpvCapabilityShader,
+                                           SpvCapabilityKernel));
 
 TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) {
   // In this case, the loop is reachable from a node without a predecessor,
@@ -270,7 +289,7 @@
     loop.SetBody("OpLoopMerge %merge %cont None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop", "entry", "cont", "merge",
                             std::make_pair("func", "Main")) +
                     types_consts() +
@@ -293,7 +312,7 @@
 
   entry.SetBody("%var = OpVariable %ptrt Function\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps(std::make_pair("func", "Main")) + types_consts() +
                     " %func    = OpFunction %voidt None %funct\n";
   str += entry >> cont;
@@ -313,7 +332,7 @@
   // This operation should only be performed in the entry block
   cont.SetBody("%var = OpVariable %ptrt Function\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps(std::make_pair("func", "Main")) + types_consts() +
                     " %func    = OpFunction %voidt None %funct\n";
 
@@ -339,7 +358,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop", "merge", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -364,7 +383,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("cont", "branch", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -396,9 +415,10 @@
   // cannot share the same merge
   if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str =
-      header(GetParam()) + nameOps("merge", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("merge", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> selection;
@@ -431,9 +451,10 @@
   // cannot share the same merge
   if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
 
-  std::string str =
-      header(GetParam()) + nameOps("merge", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("merge", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> selection;
   str += selection >> std::vector<Block>({merge, loop});
@@ -457,7 +478,7 @@
   Block entry("entry");
   Block bad("bad");
   Block end("end", SpvOpReturn);
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -481,7 +502,7 @@
   Block bad("bad");
   Block end("end", SpvOpReturn);
   Block badvalue("undef");  // This referenes the OpUndef.
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -493,13 +514,10 @@
   str += "OpFunctionEnd\n";
 
   CompileSuccessfully(str);
-  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      MatchesRegex("Block\\(s\\) \\{11\\[%11\\]\\} are referenced but not "
-                   "defined in function .\\[%Main\\]\n  %Main = OpFunction "
-                   "%void None %10\n"))
-      << str;
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpBranch must "
+                        "be the ID of an OpLabel instruction"));
 }
 
 TEST_P(ValidateCFG, BranchConditionalTrueTargetFirstBlockBad) {
@@ -510,7 +528,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody(" OpLoopMerge %entry %exit None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -538,7 +556,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpLoopMerge %merge %cont None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -570,7 +588,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -605,9 +623,10 @@
   Block middle2("middle2");
   Block end2("end2", SpvOpReturn);
 
-  std::string str =
-      header(GetParam()) + nameOps("middle2", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("middle2", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> middle;
   str += middle >> std::vector<Block>({end, middle2});
@@ -640,7 +659,7 @@
 
   if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("head", "merge", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -675,7 +694,7 @@
 
   if (is_shader) head.AppendBody("OpSelectionMerge %head None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("head", "exit", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -697,8 +716,10 @@
   }
 }
 
-TEST_P(ValidateCFG, UnreachableMerge) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+std::string GetUnreachableMergeNoMergeInst(SpvCapability cap,
+                                           spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
   Block entry("entry");
   Block branch("branch", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
@@ -706,13 +727,18 @@
   Block merge("merge", SpvOpReturn);
 
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env) && cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
-                    nameOps("branch", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+  str += types_consts() + "%func    = OpFunction %voidt None %funct\n";
   str += entry >> branch;
   str += branch >> std::vector<Block>({t, f});
   str += t;
@@ -720,12 +746,209 @@
   str += merge;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeNoMergeInst) {
+  CompileSuccessfully(
+      GetUnreachableMergeNoMergeInst(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableMergeDefinedByOpUnreachable) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+TEST_F(ValidateCFG, WebGPUUnreachableMergeNoMergeInst) {
+  CompileSuccessfully(
+      GetUnreachableMergeNoMergeInst(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, all blocks must be reachable"));
+}
+
+std::string GetUnreachableMergeTerminatedBy(SpvCapability cap,
+                                            spv_target_env env, SpvOp op) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", op);
+
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpUnreachable) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, UnreachableMergeTerminatedByOpKill) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpReturn) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpUnreachable) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpKill) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must terminate with OpUnreachable"));
+}
+
+TEST_P(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpReturn) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must terminate with OpUnreachable"));
+}
+
+std::string GetUnreachableContinueTerminatedBy(SpvCapability cap,
+                                               spv_target_env env, SpvOp op) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", op);
+
+  if (op == SpvOpBranch) target >> branch;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpUnreachable) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("targeted by 0 back-edge blocks"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, UnreachableContinueTerminatedBySpvOpKill) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("targeted by 0 back-edge blocks"));
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpReturn) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("targeted by 0 back-edge blocks"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpBranch) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpBranch));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpUnreachable) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpKill) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpReturn) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpBranch) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpBranch));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+std::string GetUnreachableMergeUnreachableMergeInst(SpvCapability cap,
+                                                    spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block body("body", SpvOpReturn);
   Block entry("entry");
   Block branch("branch", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
@@ -733,13 +956,134 @@
   Block merge("merge", SpvOpUnreachable);
 
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
 
-  std::string str = header(GetParam()) +
-                    nameOps("branch", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += body;
+  str += merge;
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += "OpFunctionEnd\n";
 
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeUnreachableMergeInst) {
+  CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeUnreachableMergeInst) {
+  CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be referenced by a reachable merge instruction"));
+}
+
+std::string GetUnreachableContinueUnreachableLoopInst(SpvCapability cap,
+                                                      spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block body("body", SpvOpReturn);
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  target >> branch;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += body;
+  str += target;
+  str += merge;
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
+  CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  if (GetParam() == SpvCapabilityShader) {
+    // Shader causes additional structured CFG checks that cause a failure.
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be "
+                          "formed between a block and a loop header."));
+
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueUnreachableLoopInst) {
+  CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be referenced by a reachable loop instruction"));
+}
+
+std::string GetUnreachableMergeWithComplexBody(SpvCapability cap,
+                                               spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+  merge.AppendBody("OpStore %dummy %one\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
   str += entry >> branch;
   str += branch >> std::vector<Block>({t, f});
   str += t;
@@ -747,31 +1091,406 @@
   str += merge;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithComplexBody) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableBlock) {
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithComplexBody) {
+  CompileSuccessfully(GetUnreachableMergeWithComplexBody(SpvCapabilityShader,
+                                                         SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("must only contain an OpLabel and OpUnreachable instruction"));
+}
+
+std::string GetUnreachableContinueWithComplexBody(SpvCapability cap,
+                                                  spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  target >> branch;
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+  target.AppendBody("OpStore %dummy %one\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueWithComplexBody) {
+  CompileSuccessfully(
+      GetUnreachableContinueWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueWithComplexBody) {
+  CompileSuccessfully(GetUnreachableContinueWithComplexBody(SpvCapabilityShader,
+                                                            SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("must only contain an OpLabel and an OpBranch instruction"));
+}
+
+std::string GetUnreachableMergeWithBranchUse(SpvCapability cap,
+                                             spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpBranch);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t >> merge;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithBranchUse(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("cannot be the target of a branch."));
+}
+
+std::string GetUnreachableMergeWithMultipleUses(SpvCapability cap,
+                                                spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+  Block duplicate("duplicate", SpvOpBranchConditional);
+
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+    duplicate.AppendBody("OpSelectionMerge %merge None\n");
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += duplicate >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithMultipleUses) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithMultipleUses(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("is already a merge block for another header"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithMultipleUses) {
+  CompileSuccessfully(GetUnreachableMergeWithMultipleUses(SpvCapabilityShader,
+                                                          SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("is already a merge block for another header"));
+}
+
+std::string GetUnreachableContinueWithBranchUse(SpvCapability cap,
+                                                spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block foo("foo", SpvOpBranch);
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  foo >> target;
+  target >> branch;
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += foo;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableContinueWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueWithBranchUse) {
+  CompileSuccessfully(GetUnreachableContinueWithBranchUse(SpvCapabilityShader,
+                                                          SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("cannot be the target of a branch."));
+}
+
+std::string GetReachableMergeAndContinue(SpvCapability cap,
+                                         spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+  Block body("body", SpvOpBranchConditional);
+  Block t("t", SpvOpBranch);
+  Block f("f", SpvOpBranch);
+
+  target >> branch;
+  body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+  t >> merge;
+  f >> target;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+    body.AppendBody("OpSelectionMerge %target None\n");
+  }
+
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", "body", "t", "f",
+                   std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({body});
+  str += body >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, ReachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetReachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUReachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetReachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+std::string GetUnreachableMergeAndContinue(SpvCapability cap,
+                                           spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+  Block body("body", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+
+  target >> branch;
+  body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+    body.AppendBody("OpSelectionMerge %target None\n");
+  }
+
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", "body", "t", "f",
+                   std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({body});
+  str += body >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetUnreachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetUnreachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("unreachable merge-blocks must terminate with OpUnreachable"));
+}
+
+std::string GetUnreachableBlock(SpvCapability cap, spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
   Block entry("entry");
   Block unreachable("unreachable");
   Block exit("exit", SpvOpReturn);
 
-  std::string str =
-      header(GetParam()) +
-      nameOps("unreachable", "exit", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
-
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
   str += entry >> exit;
   str += unreachable >> exit;
   str += exit;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableBlock) {
+  CompileSuccessfully(GetUnreachableBlock(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableBranch) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+TEST_F(ValidateCFG, WebGPUUnreachableBlock) {
+  CompileSuccessfully(
+      GetUnreachableBlock(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
+}
+
+std::string GetUnreachableBranch(SpvCapability cap, spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
   Block entry("entry");
   Block unreachable("unreachable", SpvOpBranchConditional);
   Block unreachablechildt("unreachablechildt");
@@ -780,11 +1499,19 @@
   Block exit("exit", SpvOpReturn);
 
   unreachable.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n");
-  std::string str =
-      header(GetParam()) +
-      nameOps("unreachable", "exit", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  if (cap == SpvCapabilityShader)
+    unreachable.AppendBody("OpSelectionMerge %merge None\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> exit;
   str +=
@@ -795,12 +1522,23 @@
   str += exit;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableBranch) {
+  CompileSuccessfully(GetUnreachableBranch(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, WebGPUUnreachableBranch) {
+  CompileSuccessfully(
+      GetUnreachableBranch(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
+}
+
 TEST_P(ValidateCFG, EmptyFunction) {
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     R"(%func    = OpFunction %voidt None %funct
                   %l = OpLabel
                   OpReturn
@@ -819,7 +1557,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
 
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -848,8 +1586,8 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = header(GetParam()) + nameOps("loop2", "loop2_merge") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("loop2", "loop2_merge") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop1;
@@ -888,7 +1626,7 @@
       if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
     merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch);
   }
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> if_blocks[0];
@@ -923,7 +1661,7 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop1", "loop2", "be_block", "loop2_merge") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -960,7 +1698,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
-  std::string str = header(GetParam()) + nameOps("split", "f") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("split", "f") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -993,7 +1731,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
-  std::string str = header(GetParam()) + nameOps("split") + types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("split") +
+                    types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> split;
@@ -1025,8 +1764,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
 
-  std::string str = header(GetParam()) + nameOps("loop", "back0", "back1") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("loop", "back0", "back1") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -1062,8 +1801,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
 
-  std::string str = header(GetParam()) + nameOps("cheader", "be_block") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("cheader", "be_block") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -1097,7 +1836,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) + nameOps("cont", "loop") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1132,7 +1871,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) + nameOps("cont", "loop") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1213,7 +1952,7 @@
 %funct   = OpTypeFunction %voidt
 %main    = OpFunction %voidt None %funct
 %loop    = OpLabel
-           OpLoopMerge %exit %exit None
+           OpLoopMerge %exit %loop None
            OpBranch %exit
 %exit    = OpLabel
            OpReturn
@@ -1283,7 +2022,7 @@
     inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "inner_merge", "exit") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1321,7 +2060,7 @@
   }
 
   std::string str =
-      header(GetParam()) +
+      GetDefaultHeader(GetParam()) +
       nameOps("entry", "loop", "if_head", "if_true", "if_merge", "merge") +
       types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
@@ -1351,9 +2090,10 @@
     loop.SetBody("OpLoopMerge %merge %latch None\n");
   }
 
-  std::string str =
-      header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("entry", "loop", "latch", "merge") +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> std::vector<Block>({latch, merge});
@@ -1384,9 +2124,10 @@
     loop.SetBody("OpLoopMerge %merge %loop None\n");
   }
 
-  std::string str =
-      header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("entry", "loop", "latch", "merge") +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> latch;
@@ -1882,6 +2623,134 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, SwitchCaseOrderingBad1) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %default "default"
+OpName %other "other"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %merge None
+OpSwitch %undef %default 0 %other 1 %default
+%default = OpLabel
+OpBranch %other
+%other = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  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 "
+                "immediately precede it in the OpSwitch's target list"));
+}
+
+TEST_F(ValidateCFG, SwitchCaseOrderingBad2) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %default "default"
+OpName %other "other"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %merge None
+OpSwitch %undef %default 0 %default 1 %other
+%other = OpLabel
+OpBranch %default
+%default = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  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 "
+                "immediately precede it in the OpSwitch's target list"));
+}
+
+TEST_F(ValidateCFG, SwitchMultipleDefaultWithFallThroughGood) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %first "first"
+OpName %second "second"
+OpName %third "third"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %merge None
+OpSwitch %undef %second 0 %first 1 %second 2 %third
+%first = OpLabel
+OpBranch %second
+%second = OpLabel
+OpBranch %third
+%third = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, SwitchMultipleDefaultWithFallThroughBad) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %first "first"
+OpName %second "second"
+OpName %third "third"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %merge None
+OpSwitch %undef %second 0 %second 1 %first 2 %third
+%first = OpLabel
+OpBranch %second
+%second = OpLabel
+OpBranch %third
+%third = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+}
+
 TEST_F(ValidateCFG, GoodUnreachableSelection) {
   const std::string text = R"(
 OpCapability Shader
@@ -2060,6 +2929,826 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, SwitchTargetMustBeLabel) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "foo"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+          %1 = OpFunction %void None %5
+          %6 = OpLabel
+          %7 = OpCopyObject %uint %uint_0
+               OpSelectionMerge %8 None
+               OpSwitch %uint_0 %8 0 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpSwitch must "
+                        "be IDs of an OpLabel instruction"));
+}
+
+TEST_F(ValidateCFG, BranchTargetMustBeLabel) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "foo"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+          %1 = OpFunction %void None %5
+          %2 = OpLabel
+          %7 = OpCopyObject %uint %uint_0
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpBranch must "
+                        "be the ID of an OpLabel instruction"));
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOneBlock) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpBranch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %block
+%block = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpBranchConditional) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranchConditional %undef %block %unreachable
+%block = OpLabel
+OpReturn
+%unreachable = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSwitch %undef %block1 0 %unreachable 1 %block2
+%block1 = OpLabel
+OpReturn
+%unreachable = OpLabel
+OpUnreachable
+%block2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableLoop) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %unreachable %loop None
+OpBranchConditional %undef %loop %unreachable
+%unreachable = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, UnreachableLoopBadBackedge) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%8 = OpTypeBool
+%13 = OpConstantTrue %8
+%2 = OpFunction %4 None %5
+%14 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %13 %15 %15
+%16 = OpLabel
+OpLoopMerge %17 %18 None
+OpBranch %17
+%18 = OpLabel
+OpBranch %17
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // The back-edge in this test is bad, but the validator fails to identify it
+  // because it is in an entirely unreachable section of code. Prior to #2488
+  // this code failed an assert in Construct::blocks().
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, OneContinueTwoBackedges) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 1 1 1
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%5 = OpTypeFunction %void
+%1 = OpFunction %void None %5
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %11 %9 None
+OpBranchConditional %true %11 %9
+%9 = OpLabel
+OpBranchConditional %true %10 %7
+%11 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 9 branches to the loop construct, but not "
+                        "to the loop header <ID> 7"));
+}
+
+TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpLoopMerge %undef %2 None
+OpBranchConditional %undef %2 %2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Merge Block 1[%undef] must be an OpLabel"));
+}
+
+TEST_F(ValidateCFG, LoopMergeContinueTargetNotLabel) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpLoopMerge %2 %undef None
+OpBranchConditional %undef %2 %2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Continue Target 1[%undef] must be an OpLabel"));
+}
+
+TEST_F(ValidateCFG, LoopMergeMergeBlockContinueTargetSameLabel) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpLoopMerge %2 %2 None
+OpBranchConditional %undef %2 %2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Merge Block and Continue Target must be different ids"));
+}
+
+TEST_F(ValidateCFG, LoopMergeUnrollAndDontUnroll) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 Unroll|DontUnroll
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Unroll and DontUnroll loop controls must not both be specified"));
+}
+
+TEST_F(ValidateCFG, LoopMergePeelCountAndDontUnroll) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 DontUnroll|PeelCount 1
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "PeelCount and DontUnroll loop controls must not both be specified"));
+}
+
+TEST_F(ValidateCFG, LoopMergePartialCountAndDontUnroll) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 DontUnroll|PartialCount 1
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("PartialCount and DontUnroll loop controls must not "
+                        "both be specified"));
+}
+
+TEST_F(ValidateCFG, LoopMergeIterationMultipleZero) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 IterationMultiple 0
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "IterationMultiple loop control operand must be greater than zero"));
+}
+
+TEST_F(ValidateCFG, LoopMergeIterationMultipleZeroMoreOperands) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %undef "undef"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %2 %3 MaxIterations|IterationMultiple 4 0
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %1
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "IterationMultiple loop control operand must be greater than zero"));
+}
+
+TEST_F(ValidateCFG, LoopMergeTargetsHeader) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %loop %continue None
+OpBranch %body
+%continue = OpLabel
+OpBranch %loop
+%body = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Merge Block may not be the block containing the OpLoopMerge"));
+}
+
+TEST_F(ValidateCFG, InvalidSelectionExit) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%2 = OpTypeVoid
+%3 = OpTypeBool
+%4 = OpConstantTrue %3
+%5 = OpTypeFunction %2
+%1 = OpFunction %2 None %5
+%6 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %4 %7 %8
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpBranchConditional %4 %10 %9
+%10 = OpLabel
+OpBranch %7
+%9 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, InvalidLoopExit) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%2 = OpTypeVoid
+%3 = OpTypeBool
+%4 = OpConstantTrue %3
+%5 = OpTypeFunction %2
+%1 = OpFunction %2 None %5
+%6 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %4 %7 %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranchConditional %4 %9 %11
+%11 = OpLabel
+OpBranchConditional %4 %7 %10
+%10 = OpLabel
+OpBranch %8
+%9 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, InvalidContinueExit) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%2 = OpTypeVoid
+%3 = OpTypeBool
+%4 = OpConstantTrue %3
+%5 = OpTypeFunction %2
+%1 = OpFunction %2 None %5
+%6 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %4 %7 %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranchConditional %4 %9 %10
+%10 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranchConditional %4 %8 %7
+%9 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, InvalidSelectionExitBackedge) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpUndef %2
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranchConditional %3 %8 %9
+%9 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %3 %11 %12
+%11 = OpLabel
+OpBranch %13
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+OpBranch %7
+%10 = OpLabel
+OpUnreachable
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, BreakFromSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %4 %11 %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, InvalidBreakFromSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpSwitch %5 %11 0 %12
+%12 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, BreakToOuterSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpUndef %2
+%5 = OpUndef %3
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpSwitch %5 %9 0 %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpSwitch %5 %11 0 %12
+%12 = OpLabel
+OpSelectionMerge %13 None
+OpBranchConditional %4 %13 %14
+%14 = OpLabel
+OpBranch %9
+%13 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  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"));
+}
+
+TEST_F(ValidateCFG, BreakToOuterLoop) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpUndef %2
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %9 %11 None
+OpBranch %12
+%12 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %3 %11 %13
+%13 = OpLabel
+OpBranch %8
+%11 = OpLabel
+OpBranchConditional %3 %9 %10
+%9 = OpLabel
+OpBranchConditional %3 %8 %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 13[%13] exits the loop headed by <ID> "
+                        "10[%10], but not via a structured exit"));
+}
+
+TEST_F(ValidateCFG, ContinueFromNestedSelection) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%4 = OpFunction %void None %void_fn
+%5 = OpLabel
+OpBranch %48
+%48 = OpLabel
+OpLoopMerge %47 %50 None
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %12 %37 None
+OpBranchConditional %undef %11 %12
+%11 = OpLabel
+OpSelectionMerge %31 None
+OpBranchConditional %undef %30 %31
+%30 = OpLabel
+OpSelectionMerge %37 None
+OpBranchConditional %undef %36 %37
+%36 = OpLabel
+OpBranch %37
+%37 = OpLabel
+OpBranch %10
+%31 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpSelectionMerge %55 None
+OpBranchConditional %undef %47 %55
+%55 = OpLabel
+OpBranch %47
+%50 = OpLabel
+OpBranch %48
+%47 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 /// TODO(umar): Nested CFG constructs
 
 }  // namespace
diff --git a/test/val/val_code_generator.cpp b/test/val/val_code_generator.cpp
new file mode 100644
index 0000000..62aae9c
--- /dev/null
+++ b/test/val/val_code_generator.cpp
@@ -0,0 +1,224 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/val/val_code_generator.h"
+
+#include <sstream>
+
+namespace spvtools {
+namespace val {
+namespace {
+
+std::string GetDefaultShaderCapabilities() {
+  return R"(
+OpCapability Shader
+OpCapability Geometry
+OpCapability Tessellation
+OpCapability Float64
+OpCapability Int64
+OpCapability MultiViewport
+OpCapability SampleRateShading
+)";
+}
+
+std::string GetWebGPUShaderCapabilities() {
+  return R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+)";
+}
+
+std::string GetDefaultShaderTypes() {
+  return R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%i32 = OpTypeInt 32 1
+%i64 = OpTypeInt 64 1
+%u32 = OpTypeInt 32 0
+%u64 = OpTypeInt 64 0
+%f32vec2 = OpTypeVector %f32 2
+%f32vec3 = OpTypeVector %f32 3
+%f32vec4 = OpTypeVector %f32 4
+%f64vec2 = OpTypeVector %f64 2
+%f64vec3 = OpTypeVector %f64 3
+%f64vec4 = OpTypeVector %f64 4
+%u32vec2 = OpTypeVector %u32 2
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%u32vec4 = OpTypeVector %u32 4
+%u64vec2 = OpTypeVector %u64 2
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_h = OpConstant %f32 0.5
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
+
+%u32arr2 = OpTypeArray %u32 %u32_2
+%u32arr3 = OpTypeArray %u32 %u32_3
+%u32arr4 = OpTypeArray %u32 %u32_4
+%u64arr2 = OpTypeArray %u64 %u32_2
+%u64arr3 = OpTypeArray %u64 %u32_3
+%u64arr4 = OpTypeArray %u64 %u32_4
+%f32arr2 = OpTypeArray %f32 %u32_2
+%f32arr3 = OpTypeArray %f32 %u32_3
+%f32arr4 = OpTypeArray %f32 %u32_4
+%f64arr2 = OpTypeArray %f64 %u32_2
+%f64arr3 = OpTypeArray %f64 %u32_3
+%f64arr4 = OpTypeArray %f64 %u32_4
+
+%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
+%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
+%f64vec4arr3 = OpTypeArray %f64vec4 %u32_3
+)";
+}
+
+std::string GetWebGPUShaderTypes() {
+  return R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%f32vec2 = OpTypeVector %f32 2
+%f32vec3 = OpTypeVector %f32 3
+%f32vec4 = OpTypeVector %f32 4
+%u32vec2 = OpTypeVector %u32 2
+%u32vec3 = OpTypeVector %u32 3
+%u32vec4 = OpTypeVector %u32 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_h = OpConstant %f32 0.5
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+
+%u32arr2 = OpTypeArray %u32 %u32_2
+%u32arr3 = OpTypeArray %u32 %u32_3
+%u32arr4 = OpTypeArray %u32 %u32_4
+%f32arr2 = OpTypeArray %f32 %u32_2
+%f32arr3 = OpTypeArray %f32 %u32_3
+%f32arr4 = OpTypeArray %f32 %u32_4
+
+%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
+%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
+)";
+}
+
+}  // namespace
+
+CodeGenerator CodeGenerator::GetDefaultShaderCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = GetDefaultShaderCapabilities();
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.types_ = GetDefaultShaderTypes();
+  return generator;
+}
+
+CodeGenerator CodeGenerator::GetWebGPUShaderCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = GetWebGPUShaderCapabilities();
+  generator.memory_model_ = "OpMemoryModel Logical VulkanKHR\n";
+  generator.extensions_ = "OpExtension \"SPV_KHR_vulkan_memory_model\"\n";
+  generator.types_ = GetWebGPUShaderTypes();
+  return generator;
+}
+
+std::string CodeGenerator::Build() const {
+  std::ostringstream ss;
+
+  ss << capabilities_;
+  ss << extensions_;
+  ss << memory_model_;
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << "OpEntryPoint " << entry_point.execution_model << " %"
+       << entry_point.name << " \"" << entry_point.name << "\" "
+       << entry_point.interfaces << "\n";
+  }
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << entry_point.execution_modes << "\n";
+  }
+
+  ss << before_types_;
+  ss << types_;
+  ss << after_types_;
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << "\n";
+    ss << "%" << entry_point.name << " = OpFunction %void None %func\n";
+    ss << "%" << entry_point.name << "_entry = OpLabel\n";
+    ss << entry_point.body;
+    ss << "\nOpReturn\nOpFunctionEnd\n";
+  }
+
+  ss << add_at_the_end_;
+
+  return ss.str();
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_code_generator.h b/test/val/val_code_generator.h
new file mode 100644
index 0000000..e580ddf
--- /dev/null
+++ b/test/val/val_code_generator.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Utility class used to generate SPIR-V code strings for tests
+
+#include <string>
+#include <vector>
+
+namespace spvtools {
+namespace val {
+
+struct EntryPoint {
+  std::string name;
+  std::string execution_model;
+  std::string execution_modes;
+  std::string body;
+  std::string interfaces;
+};
+
+class CodeGenerator {
+ public:
+  static CodeGenerator GetDefaultShaderCodeGenerator();
+  static CodeGenerator GetWebGPUShaderCodeGenerator();
+
+  std::string Build() const;
+
+  std::vector<EntryPoint> entry_points_;
+  std::string capabilities_;
+  std::string extensions_;
+  std::string memory_model_;
+  std::string before_types_;
+  std::string types_;
+  std::string after_types_;
+  std::string add_at_the_end_;
+};
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index bf7f15d..74b6f20 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -17,6 +17,7 @@
 
 #include "gmock/gmock.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
@@ -25,6 +26,7 @@
 
 using ::testing::HasSubstr;
 using ::testing::Not;
+using ::testing::Values;
 
 using ValidateComposites = spvtest::ValidateBase<bool>;
 
@@ -1467,6 +1469,83 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateComposites, CoopMatConstantCompositeMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%f32_1 = OpConstant %f32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f32_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpConstantComposite Constituent <id> '11[%float_1]' type does "
+                "not match the Result Type <id> '10[%10]'s component type."));
+}
+
+TEST_F(ValidateComposites, CoopMatCompositeConstructMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%f32_1 = OpConstant %f32 1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%f16mat_1 = OpCompositeConstruct %f16mat %f32_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Constituent type to be equal to the component type"));
+}
+
 TEST_F(ValidateComposites, ExtractDynamicLabelIndex) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1491,6 +1570,414 @@
               HasSubstr("Expected Index to be int scalar"));
 }
 
+TEST_F(ValidateComposites, CopyLogicalSameType) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%struct = OpTypeStruct
+%const_struct = OpConstantComposite %struct
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result Type must not equal the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalSameStructDifferentId) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%struct1 = OpTypeStruct
+%struct2 = OpTypeStruct
+%const_struct = OpConstantComposite %struct1
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayDifferentLength) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%array1 = OpTypeArray %int %int_4
+%array2 = OpTypeArray %int %int_5
+%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayDifferentElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array1 = OpTypeArray %int %int_4
+%array2 = OpTypeArray %float %int_4
+%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayLogicallyMatchedElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%inner1 = OpTypeArray %int %int_1
+%inner2 = OpTypeArray %int %int_1
+%array1 = OpTypeArray %inner1 %int_1
+%array2 = OpTypeArray %inner2 %int_1
+%const_inner = OpConstantComposite %inner1 %int_1
+%const_array = OpConstantComposite %array1 %const_inner
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructDifferentNumberElements) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct1 = OpTypeStruct
+%struct2 = OpTypeStruct %int
+%const_struct = OpConstantComposite %struct1
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructDifferentElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%struct1 = OpTypeStruct %int %uint
+%struct2 = OpTypeStruct %int %int
+%const_struct = OpConstantComposite %struct1 %int_0 %uint_0
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructLogicallyMatch) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%array1 = OpTypeArray %int %int_1
+%array2 = OpTypeArray %int %int_1
+%struct1 = OpTypeStruct %int %array1
+%struct2 = OpTypeStruct %int %array2
+%const_array = OpConstantComposite %array1 %int_1
+%const_struct = OpConstantComposite %struct1 %int_1 %const_array
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+using ValidateSmallComposites = spvtest::ValidateBase<std::string>;
+
+CodeGenerator GetSmallCompositesCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability UniformAndStorageBuffer8BitAccess
+)";
+  generator.extensions_ = R"(
+OpExtension "SPV_KHR_16bit_storage"
+OpExtension "SPV_KHR_8bit_storage"
+)";
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.before_types_ = R"(
+OpDecorate %char_block Block
+OpMemberDecorate %char_block 0 Offset 0
+OpDecorate %short_block Block
+OpMemberDecorate %short_block 0 Offset 0
+OpDecorate %half_block Block
+OpMemberDecorate %half_block 0 Offset 0
+)";
+  generator.types_ = R"(
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%char = OpTypeInt 8 0
+%char2 = OpTypeVector %char 2
+%short = OpTypeInt 16 0
+%short2 = OpTypeVector %short 2
+%half = OpTypeFloat 16
+%half2 = OpTypeVector %half 2
+%char_block = OpTypeStruct %char2
+%short_block = OpTypeStruct %short2
+%half_block = OpTypeStruct %half2
+%ptr_ssbo_char_block = OpTypePointer StorageBuffer %char_block
+%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2
+%ptr_ssbo_char = OpTypePointer StorageBuffer %char
+%ptr_ssbo_short_block = OpTypePointer StorageBuffer %short_block
+%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2
+%ptr_ssbo_short = OpTypePointer StorageBuffer %short
+%ptr_ssbo_half_block = OpTypePointer StorageBuffer %half_block
+%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2
+%ptr_ssbo_half = OpTypePointer StorageBuffer %half
+%void_fn = OpTypeFunction %void
+%char_var = OpVariable %ptr_ssbo_char_block StorageBuffer
+%short_var = OpVariable %ptr_ssbo_short_block StorageBuffer
+%half_var = OpVariable %ptr_ssbo_half_block StorageBuffer
+)";
+  generator.after_types_ = R"(
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%char2_gep = OpAccessChain %ptr_ssbo_char2 %char_var %int_0
+%ld_char2 = OpLoad %char2 %char2_gep
+%char_gep = OpAccessChain %ptr_ssbo_char %char_var %int_0 %int_0
+%ld_char = OpLoad %char %char_gep
+%short2_gep = OpAccessChain %ptr_ssbo_short2 %short_var %int_0
+%ld_short2 = OpLoad %short2 %short2_gep
+%short_gep = OpAccessChain %ptr_ssbo_short %short_var %int_0 %int_0
+%ld_short = OpLoad %short %short_gep
+%half2_gep = OpAccessChain %ptr_ssbo_half2 %half_var %int_0
+%ld_half2 = OpLoad %half2 %half2_gep
+%half_gep = OpAccessChain %ptr_ssbo_half %half_var %int_0 %int_0
+%ld_half = OpLoad %half %half_gep
+)";
+  generator.add_at_the_end_ = R"(
+OpReturn
+OpFunctionEnd
+)";
+  return generator;
+}
+
+TEST_P(ValidateSmallComposites, VectorExtractDynamic) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst =
+      "%inst = OpVectorExtractDynamic %" + type + " %ld_" + type + "2 %int_0\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot extract from a vector of 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, VectorInsertDynamic) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst = "%inst = OpVectorInsertDynamic %" + type + "2 %ld_" +
+                     type + "2 %ld_" + type + " %int_0\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot insert into a vector of 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, VectorShuffle) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst = "%inst = OpVectorShuffle %" + type + "2 %ld_" + type +
+                     "2 %ld_" + type + "2 0 0\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot shuffle a vector of 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, CompositeConstruct) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst = "%inst = OpCompositeConstruct %" + type + "2 %ld_" + type +
+                     " %ld_" + type + "\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot create a composite containing 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, CompositeExtract) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst =
+      "%inst = OpCompositeExtract %" + type + " %ld_" + type + "2 0\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot extract from a composite of 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, CompositeInsert) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst = "%inst = OpCompositeInsert %" + type + "2 %ld_" + type +
+                     " %ld_" + type + "2 0\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot insert into a composite of 8- or 16-bit types"));
+}
+
+TEST_P(ValidateSmallComposites, CopyObject) {
+  std::string type = GetParam();
+  CodeGenerator generator = GetSmallCompositesCodeGenerator();
+  std::string inst = "%inst = OpCopyObject %" + type + "2 %ld_" + type + "2\n";
+  generator.after_types_ += inst;
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+INSTANTIATE_TEST_SUITE_P(SmallCompositeInstructions, ValidateSmallComposites,
+                         Values("char", "short", "half"));
+
+TEST_F(ValidateComposites, HalfMatrixCannotTranspose) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 RowMajor
+OpMemberDecorate %block 0 MatrixStride 8
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 16
+%float2 = OpTypeVector %float 2
+%mat2x2 = OpTypeMatrix %float2 2
+%block = OpTypeStruct %mat2x2
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%ptr_ssbo_mat2x2 = OpTypePointer StorageBuffer %mat2x2
+%var = OpVariable %ptr_ssbo_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_ssbo_mat2x2 %var %int_0
+%ld = OpLoad %mat2x2 %gep
+%inst = OpTranspose %mat2x2 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot transpose matrices of 16-bit floats"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_constants_test.cpp b/test/val/val_constants_test.cpp
index 824d6fd..72ce8df 100644
--- a/test/val/val_constants_test.cpp
+++ b/test/val/val_constants_test.cpp
@@ -22,14 +22,17 @@
 
 #include "gmock/gmock.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+using ::testing::Combine;
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::Values;
 using ::testing::ValuesIn;
 
 using ValidateConstant = spvtest::ValidateBase<bool>;
@@ -84,7 +87,7 @@
   { SPV_ENV_UNIVERSAL_1_0, kShaderPreamble kBasicTypes STR, true, "" }
 #define GOOD_KERNEL_10(STR) \
   { SPV_ENV_UNIVERSAL_1_0, kKernelPreamble kBasicTypes STR, true, "" }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     UniversalInShader, ValidateConstantOp,
     ValuesIn(std::vector<ConstantOpCase>{
         // TODO(dneto): Conversions must change width.
@@ -141,7 +144,7 @@
             "%v = OpSpecConstantOp %bool SGreaterThanEqual %uint_0 %uint_0"),
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     UniversalInKernel, ValidateConstantOp,
     ValuesIn(std::vector<ConstantOpCase>{
         // TODO(dneto): Conversions must change width.
@@ -198,7 +201,61 @@
             "%v = OpSpecConstantOp %bool SGreaterThanEqual %uint_0 %uint_0"),
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    UConvert, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        {SPV_ENV_UNIVERSAL_1_0,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_1,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_4,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_0,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_1,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_4,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
     KernelInKernel, ValidateConstantOp,
     ValuesIn(std::vector<ConstantOpCase>{
         // TODO(dneto): Conversions must change width.
@@ -235,7 +292,7 @@
         "Specialization constant operation " NAME                  \
         " requires Kernel capability"                              \
   }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KernelInShader, ValidateConstantOp,
     ValuesIn(std::vector<ConstantOpCase>{
         // TODO(dneto): Conversions must change width.
@@ -280,7 +337,7 @@
                       "InBoundsPtrAccessChain"),
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     UConvertInAMD_gpu_shader_int16, ValidateConstantOp,
     ValuesIn(std::vector<ConstantOpCase>{
         // SPV_AMD_gpu_shader_int16 should enable UConvert for OpSpecConstantOp
@@ -294,6 +351,97 @@
          true, ""},
     }));
 
+TEST_F(ValidateConstant, SpecConstantUConvert1p3Binary1p4EnvBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%const = OpSpecConstantOp %int UConvert %int0
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Prior to SPIR-V 1.4, specialization constant operation UConvert "
+          "requires Kernel capability or extension SPV_AMD_gpu_shader_int16"));
+}
+
+using SmallStorageConstants = spvtest::ValidateBase<std::string>;
+
+CodeGenerator GetSmallStorageCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability StoragePushConstant16
+OpCapability StorageInputOutput16
+OpCapability UniformAndStorageBuffer8BitAccess
+OpCapability StoragePushConstant8
+)";
+  generator.extensions_ = R"(
+OpExtension "SPV_KHR_16bit_storage"
+OpExtension "SPV_KHR_8bit_storage"
+)";
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.types_ = R"(
+%short = OpTypeInt 16 0
+%short2 = OpTypeVector %short 2
+%char = OpTypeInt 8 0
+%char2 = OpTypeVector %char 2
+%half = OpTypeFloat 16
+%half2 = OpTypeVector %half 2
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+)";
+  return generator;
+}
+
+TEST_P(SmallStorageConstants, SmallConstant) {
+  std::string constant = GetParam();
+  CodeGenerator generator = GetSmallStorageCodeGenerator();
+  generator.after_types_ += constant + "\n";
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot form constants of 8- or 16-bit types"));
+}
+
+// Constant composites would be caught through scalar constants.
+INSTANTIATE_TEST_SUITE_P(
+    SmallConstants, SmallStorageConstants,
+    Values("%c = OpConstant %char 0", "%c = OpConstantNull %char2",
+           "%c = OpConstant %short 0", "%c = OpConstantNull %short",
+           "%c = OpConstant %half 0", "%c = OpConstantNull %half",
+           "%c = OpSpecConstant %char 0", "%c = OpSpecConstant %short 0",
+           "%c = OpSpecConstant %half 0",
+           "%c = OpSpecConstantOp %char SConvert %int_0",
+           "%c = OpSpecConstantOp %short SConvert %int_0",
+           "%c = OpSpecConstantOp %half FConvert %float_0"));
+
+TEST_F(ValidateConstant, NullPointerTo16BitStorageOk) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability Linkage
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+%half = OpTypeFloat 16
+%ptr_ssbo_half = OpTypePointer StorageBuffer %half
+%null_ptr = OpConstantNull %ptr_ssbo_half
+)";
+
+  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_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 5e4ad49..8851af3 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -18,6 +18,7 @@
 
 #include "gmock/gmock.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
@@ -26,6 +27,7 @@
 
 using ::testing::HasSubstr;
 using ::testing::Not;
+using ::testing::Values;
 
 using ValidateConversion = spvtest::ValidateBase<bool>;
 
@@ -639,170 +641,6 @@
           "Expected input type to be equal to Result Type: QuantizeToF16"));
 }
 
-TEST_F(ValidateConversion, ConvertFToS8BitStorage) {
-  const std::string capabilities_and_extensions = R"(
-OpCapability StorageBuffer8BitAccess
-OpExtension "SPV_KHR_8bit_storage"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-)";
-
-  const std::string decorations = R"(
-OpDecorate %ssbo Block
-OpDecorate %ssbo Binding 0
-OpDecorate %ssbo DescriptorSet 0
-OpMemberDecorate %ssbo 0 Offset 0
-)";
-
-  const std::string types = R"(
-%i8 = OpTypeInt 8 1
-%i8ptr = OpTypePointer StorageBuffer %i8
-%ssbo = OpTypeStruct %i8
-%ssboptr = OpTypePointer StorageBuffer %ssbo
-)";
-
-  const std::string variables = R"(
-%var = OpVariable %ssboptr StorageBuffer
-)";
-
-  const std::string body = R"(
-%val = OpConvertFToS %i8 %f32_2
-%accesschain = OpAccessChain %i8ptr %var %u32_0
-OpStore %accesschain %val
-)";
-
-  CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions,
-                                         decorations, types, variables)
-                          .c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Invalid cast to 8-bit integer from a floating-point: ConvertFToS"));
-}
-
-TEST_F(ValidateConversion, ConvertFToU8BitStorage) {
-  const std::string capabilities_and_extensions = R"(
-OpCapability StorageBuffer8BitAccess
-OpExtension "SPV_KHR_8bit_storage"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-)";
-
-  const std::string decorations = R"(
-OpDecorate %ssbo Block
-OpDecorate %ssbo Binding 0
-OpDecorate %ssbo DescriptorSet 0
-OpMemberDecorate %ssbo 0 Offset 0
-)";
-
-  const std::string types = R"(
-%u8 = OpTypeInt 8 0
-%u8ptr = OpTypePointer StorageBuffer %u8
-%ssbo = OpTypeStruct %u8
-%ssboptr = OpTypePointer StorageBuffer %ssbo
-)";
-
-  const std::string variables = R"(
-%var = OpVariable %ssboptr StorageBuffer
-)";
-
-  const std::string body = R"(
-%val = OpConvertFToU %u8 %f32_2
-%accesschain = OpAccessChain %u8ptr %var %u32_0
-OpStore %accesschain %val
-)";
-
-  CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions,
-                                         decorations, types, variables)
-                          .c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Invalid cast to 8-bit integer from a floating-point: ConvertFToU"));
-}
-
-TEST_F(ValidateConversion, ConvertSToF8BitStorage) {
-  const std::string capabilities_and_extensions = R"(
-OpCapability StorageBuffer8BitAccess
-OpExtension "SPV_KHR_8bit_storage"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-)";
-
-  const std::string decorations = R"(
-OpDecorate %ssbo Block
-OpDecorate %ssbo Binding 0
-OpDecorate %ssbo DescriptorSet 0
-OpMemberDecorate %ssbo 0 Offset 0
-)";
-
-  const std::string types = R"(
-%i8 = OpTypeInt 8 1
-%i8ptr = OpTypePointer StorageBuffer %i8
-%ssbo = OpTypeStruct %i8
-%ssboptr = OpTypePointer StorageBuffer %ssbo
-)";
-
-  const std::string variables = R"(
-%var = OpVariable %ssboptr StorageBuffer
-)";
-
-  const std::string body = R"(
-%accesschain = OpAccessChain %i8ptr %var %u32_0
-%load = OpLoad %i8 %accesschain
-%val = OpConvertSToF %f32 %load
-)";
-
-  CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions,
-                                         decorations, types, variables)
-                          .c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Invalid cast to floating-point from an 8-bit integer: ConvertSToF"));
-}
-
-TEST_F(ValidateConversion, ConvertUToF8BitStorage) {
-  const std::string capabilities_and_extensions = R"(
-OpCapability StorageBuffer8BitAccess
-OpExtension "SPV_KHR_8bit_storage"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-)";
-
-  const std::string decorations = R"(
-OpDecorate %ssbo Block
-OpDecorate %ssbo Binding 0
-OpDecorate %ssbo DescriptorSet 0
-OpMemberDecorate %ssbo 0 Offset 0
-)";
-
-  const std::string types = R"(
-%u8 = OpTypeInt 8 0
-%u8ptr = OpTypePointer StorageBuffer %u8
-%ssbo = OpTypeStruct %u8
-%ssboptr = OpTypePointer StorageBuffer %ssbo
-)";
-
-  const std::string variables = R"(
-%var = OpVariable %ssboptr StorageBuffer
-)";
-
-  const std::string body = R"(
-%accesschain = OpAccessChain %u8ptr %var %u32_0
-%load = OpLoad %u8 %accesschain
-%val = OpConvertUToF %f32 %load
-)";
-
-  CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions,
-                                         decorations, types, variables)
-                          .c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Invalid cast to floating-point from an 8-bit integer: ConvertUToF"));
-}
-
 TEST_F(ValidateConversion, ConvertPtrToUSuccess) {
   const std::string body = R"(
 %ptr = OpVariable %f32ptr_func Function
@@ -1184,6 +1022,172 @@
                 "GenericCastToPtrExplicit"));
 }
 
+TEST_F(ValidateConversion, CoopMatConversionSuccess) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_8 %u32_8
+%u16mat = OpTypeCooperativeMatrixNV %u16 %subgroup %u32_8 %u32_8
+%u32mat = OpTypeCooperativeMatrixNV %u32 %subgroup %u32_8 %u32_8
+%s16mat = OpTypeCooperativeMatrixNV %s16 %subgroup %u32_8 %u32_8
+%s32mat = OpTypeCooperativeMatrixNV %s32 %subgroup %u32_8 %u32_8
+
+%f16_1 = OpConstant %f16 1
+%f32_1 = OpConstant %f32 1
+%u16_1 = OpConstant %u16 1
+%u32_1 = OpConstant %u32 1
+%s16_1 = OpConstant %s16 1
+%s32_1 = OpConstant %s32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+%f32mat_1 = OpConstantComposite %f32mat %f32_1
+%u16mat_1 = OpConstantComposite %u16mat %u16_1
+%u32mat_1 = OpConstantComposite %u32mat %u32_1
+%s16mat_1 = OpConstantComposite %s16mat %s16_1
+%s32mat_1 = OpConstantComposite %s32mat %s32_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val11 = OpConvertFToU %u16mat %f16mat_1
+%val12 = OpConvertFToU %u32mat %f16mat_1
+%val13 = OpConvertFToS %s16mat %f16mat_1
+%val14 = OpConvertFToS %s32mat %f16mat_1
+%val15 = OpFConvert %f32mat %f16mat_1
+
+%val21 = OpConvertFToU %u16mat %f32mat_1
+%val22 = OpConvertFToU %u32mat %f32mat_1
+%val23 = OpConvertFToS %s16mat %f32mat_1
+%val24 = OpConvertFToS %s32mat %f32mat_1
+%val25 = OpFConvert %f16mat %f32mat_1
+
+%val31 = OpConvertUToF %f16mat %u16mat_1
+%val32 = OpConvertUToF %f32mat %u16mat_1
+%val33 = OpUConvert %u32mat %u16mat_1
+%val34 = OpSConvert %s32mat %u16mat_1
+
+%val41 = OpConvertSToF %f16mat %s16mat_1
+%val42 = OpConvertSToF %f32mat %s16mat_1
+%val43 = OpUConvert %u32mat %s16mat_1
+%val44 = OpSConvert %s32mat %s16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, CoopMatConversionShapesMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_4 = OpConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_4 %u32_4
+
+%f16_1 = OpConstant %f16 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val15 = OpFConvert %f32mat %f16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected rows of Matrix type and Result Type to be identical"));
+}
+
+TEST_F(ValidateConversion, CoopMatConversionShapesMismatchPass) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_4 = OpSpecConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_4 %u32_4
+
+%f16_1 = OpConstant %f16 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val15 = OpFConvert %f32mat %f16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateConversion, BitcastSuccess) {
   const std::string body = R"(
 %ptr = OpVariable %f32ptr_func Function
@@ -1414,6 +1418,148 @@
                         "PhysicalStorageBufferEXT: ConvertPtrToU"));
 }
 
+using ValidateSmallConversions = spvtest::ValidateBase<std::string>;
+
+CodeGenerator GetSmallConversionsCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpCapability UniformAndStorageBuffer8BitAccess
+)";
+  generator.extensions_ = R"(
+OpExtension "SPV_KHR_16bit_storage"
+OpExtension "SPV_KHR_8bit_storage"
+)";
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.before_types_ = R"(
+OpDecorate %char_block Block
+OpMemberDecorate %char_block 0 Offset 0
+OpDecorate %short_block Block
+OpMemberDecorate %short_block 0 Offset 0
+OpDecorate %half_block Block
+OpMemberDecorate %half_block 0 Offset 0
+OpDecorate %int_block Block
+OpMemberDecorate %int_block 0 Offset 0
+OpDecorate %float_block Block
+OpMemberDecorate %float_block 0 Offset 0
+)";
+  generator.types_ = R"(
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int2 = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%float2 = OpTypeVector %float 2
+%char = OpTypeInt 8 0
+%char2 = OpTypeVector %char 2
+%short = OpTypeInt 16 0
+%short2 = OpTypeVector %short 2
+%half = OpTypeFloat 16
+%half2 = OpTypeVector %half 2
+%char_block = OpTypeStruct %char2
+%short_block = OpTypeStruct %short2
+%half_block = OpTypeStruct %half2
+%int_block = OpTypeStruct %int2
+%float_block = OpTypeStruct %float2
+%ptr_ssbo_char_block = OpTypePointer StorageBuffer %char_block
+%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2
+%ptr_ssbo_char = OpTypePointer StorageBuffer %char
+%ptr_ssbo_short_block = OpTypePointer StorageBuffer %short_block
+%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2
+%ptr_ssbo_short = OpTypePointer StorageBuffer %short
+%ptr_ssbo_half_block = OpTypePointer StorageBuffer %half_block
+%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2
+%ptr_ssbo_half = OpTypePointer StorageBuffer %half
+%ptr_ssbo_int_block = OpTypePointer StorageBuffer %int_block
+%ptr_ssbo_int2 = OpTypePointer StorageBuffer %int2
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_ssbo_float_block = OpTypePointer StorageBuffer %float_block
+%ptr_ssbo_float2 = OpTypePointer StorageBuffer %float2
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%void_fn = OpTypeFunction %void
+%char_var = OpVariable %ptr_ssbo_char_block StorageBuffer
+%short_var = OpVariable %ptr_ssbo_short_block StorageBuffer
+%half_var = OpVariable %ptr_ssbo_half_block StorageBuffer
+%int_var = OpVariable %ptr_ssbo_int_block StorageBuffer
+%float_var = OpVariable %ptr_ssbo_float_block StorageBuffer
+)";
+  generator.after_types_ = R"(
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+%char2_gep = OpAccessChain %ptr_ssbo_char2 %char_var %int_0
+%ld_char2 = OpLoad %char2 %char2_gep
+%char_gep = OpAccessChain %ptr_ssbo_char %char_var %int_0 %int_0
+%ld_char = OpLoad %char %char_gep
+%short2_gep = OpAccessChain %ptr_ssbo_short2 %short_var %int_0
+%ld_short2 = OpLoad %short2 %short2_gep
+%short_gep = OpAccessChain %ptr_ssbo_short %short_var %int_0 %int_0
+%ld_short = OpLoad %short %short_gep
+%half2_gep = OpAccessChain %ptr_ssbo_half2 %half_var %int_0
+%ld_half2 = OpLoad %half2 %half2_gep
+%half_gep = OpAccessChain %ptr_ssbo_half %half_var %int_0 %int_0
+%ld_half = OpLoad %half %half_gep
+%int2_gep = OpAccessChain %ptr_ssbo_int2 %int_var %int_0
+%ld_int2 = OpLoad %int2 %int2_gep
+%int_gep = OpAccessChain %ptr_ssbo_int %int_var %int_0 %int_0
+%ld_int = OpLoad %int %int_gep
+%float2_gep = OpAccessChain %ptr_ssbo_float2 %float_var %int_0
+%ld_float2 = OpLoad %float2 %float2_gep
+%float_gep = OpAccessChain %ptr_ssbo_float %float_var %int_0 %int_0
+%ld_float = OpLoad %float %float_gep
+)";
+  generator.add_at_the_end_ = R"(
+OpReturn
+OpFunctionEnd
+)";
+  return generator;
+}
+
+TEST_P(ValidateSmallConversions, Instruction) {
+  CodeGenerator generator = GetSmallConversionsCodeGenerator();
+  generator.after_types_ += GetParam() + "\n";
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "8- or 16-bit types can only be used with width-only conversions"));
+}
+
+INSTANTIATE_TEST_SUITE_P(SmallConversionInstructions, ValidateSmallConversions,
+                         Values("%inst = OpConvertFToU %char %ld_float",
+                                "%inst = OpConvertFToU %char2 %ld_float2",
+                                "%inst = OpConvertFToU %short %ld_float",
+                                "%inst = OpConvertFToU %short2 %ld_float2",
+                                "%inst = OpConvertFToU %int %ld_half",
+                                "%inst = OpConvertFToU %int2 %ld_half2",
+                                "%inst = OpConvertFToS %char %ld_float",
+                                "%inst = OpConvertFToS %char2 %ld_float2",
+                                "%inst = OpConvertFToS %short %ld_float",
+                                "%inst = OpConvertFToS %short2 %ld_float2",
+                                "%inst = OpConvertFToS %int %ld_half",
+                                "%inst = OpConvertFToS %int2 %ld_half2",
+                                "%inst = OpConvertSToF %float %ld_char",
+                                "%inst = OpConvertSToF %float2 %ld_char2",
+                                "%inst = OpConvertSToF %float %ld_short",
+                                "%inst = OpConvertSToF %float2 %ld_short2",
+                                "%inst = OpConvertSToF %half %ld_int",
+                                "%inst = OpConvertSToF %half2 %ld_int2",
+                                "%inst = OpConvertUToF %float %ld_char",
+                                "%inst = OpConvertUToF %float2 %ld_char2",
+                                "%inst = OpConvertUToF %float %ld_short",
+                                "%inst = OpConvertUToF %float2 %ld_short2",
+                                "%inst = OpConvertUToF %half %ld_int",
+                                "%inst = OpConvertUToF %half2 %ld_int2",
+                                "%inst = OpBitcast %half %ld_short",
+                                "%inst = OpBitcast %half2 %ld_short2",
+                                "%inst = OpBitcast %short %ld_half",
+                                "%inst = OpBitcast %short2 %ld_half2"));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index 0aded92..4690f97 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -717,8 +717,7 @@
 }
 
 TEST_F(ValidateData, ext_16bit_storage_caps_allow_free_fp_rounding_mode) {
-  for (const char* cap : {"StorageUniform16", "StorageUniformBufferBlock16",
-                          "StoragePushConstant16", "StorageInputOutput16"}) {
+  for (const char* cap : {"StorageUniform16", "StorageUniformBufferBlock16"}) {
     for (const char* mode : {"RTE", "RTZ", "RTP", "RTN"}) {
       std::string str = std::string(R"(
         OpCapability Shader
@@ -778,9 +777,10 @@
       ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env));
       EXPECT_THAT(
           getDiagnosticString(),
-          HasSubstr("Operand 2 of Decorate requires one of these capabilities: "
-                    "StorageBuffer16BitAccess StorageUniform16 "
-                    "StoragePushConstant16 StorageInputOutput16"));
+          HasSubstr(
+              "Operand 2 of Decorate requires one of these capabilities: "
+              "StorageBuffer16BitAccess UniformAndStorageBuffer16BitAccess "
+              "StoragePushConstant16 StorageInputOutput16"));
     }
   }
 }
@@ -934,6 +934,23 @@
                         "OpTypeStruct %_runtimearr_uint %uint\n"));
 }
 
+TEST_F(ValidateData, invalid_forward_reference_in_array) {
+  std::string str = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+       %uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_3_uint_1 = OpTypeArray %_arr_3_uint_1 %uint_1
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_3);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Forward reference operands in an OpTypeArray must "
+                        "first be declared using OpTypeForwardPointer."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 827ebf1..c267dbf 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -19,17 +19,31 @@
 
 #include "gmock/gmock.h"
 #include "source/val/decoration.h"
+#include "test/test_fixture.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+using ::testing::Combine;
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::Values;
+
+struct TestResult {
+  TestResult(spv_result_t in_validation_result = SPV_SUCCESS,
+             const std::string& in_error_str = "")
+      : validation_result(in_validation_result), error_str(in_error_str) {}
+  spv_result_t validation_result;
+  const std::string error_str;
+};
 
 using ValidateDecorations = spvtest::ValidateBase<bool>;
+using ValidateWebGPUCombineDecorationResult =
+    spvtest::ValidateBase<std::tuple<const char*, TestResult>>;
 
 TEST_F(ValidateDecorations, ValidateOpDecorateRegistration) {
   std::string spirv = R"(
@@ -115,7 +129,7 @@
                OpCapability Linkage
                OpMemoryModel Logical GLSL450
                OpDecorate %1 DescriptorSet 0
-               OpDecorate %1 NonWritable
+               OpDecorate %1 RelaxedPrecision
                OpDecorate %1 Restrict
           %1 = OpDecorationGroup
                OpGroupDecorate %1 %2 %3
@@ -136,9 +150,10 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 
   // Decoration group has 3 decorations.
-  auto expected_decorations = std::vector<Decoration>{
-      Decoration(SpvDecorationDescriptorSet, {0}),
-      Decoration(SpvDecorationNonWritable), Decoration(SpvDecorationRestrict)};
+  auto expected_decorations =
+      std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
+                              Decoration(SpvDecorationRelaxedPrecision),
+                              Decoration(SpvDecorationRestrict)};
 
   // Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is
   // the decoration group id) also has all the decorations.
@@ -1496,6 +1511,158 @@
       << getDiagnosticString();
 }
 
+TEST_F(ValidateDecorations, BlockCantAppearWithinABlockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+               OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 12
+               OpDecorate %S Block
+               OpDecorate %S2 Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S2 = OpTypeStruct %float %float
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BufferblockCantAppearWithinABufferblockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+               OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 12
+               OpDecorate %S BufferBlock
+               OpDecorate %S3 BufferBlock
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S3 = OpTypeStruct %float %float
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BufferblockCantAppearWithinABlockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+               OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 12
+               OpDecorate %S Block
+               OpDecorate %S3 BufferBlock
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S3 = OpTypeStruct %float %float
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BlockCantAppearWithinABufferblockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+              OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 16
+               OpMemberDecorate %S4 0 Offset 0
+               OpMemberDecorate %S4 1 Offset 12
+               OpDecorate %S BufferBlock
+               OpDecorate %S4 Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S4 = OpTypeStruct %float %float
+         %S3 = OpTypeStruct %float %S4
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
 TEST_F(ValidateDecorations, BlockLayoutForbidsTightScalarVec3PackingBad) {
   // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1666
   std::string spirv = R"(
@@ -1933,7 +2100,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
-TEST_F(ValidateDecorations, BlockArrayBaseAlignmentGood) {
+TEST_F(ValidateDecorations, BlockArrayExtendedAlignmentGood) {
   // For uniform buffer, Array base alignment is 16, and ArrayStride
   // must be a multiple of 16.
   std::string spirv = R"(
@@ -1966,7 +2133,7 @@
       << getDiagnosticString();
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentBad) {
   // For uniform buffer, Array base alignment is 16.
   std::string spirv = R"(
                OpCapability Shader
@@ -2003,7 +2170,7 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithRelaxedLayoutStillBad) {
   // For uniform buffer, Array base alignment is 16, and ArrayStride
   // must be a multiple of 16.  This case uses relaxed block layout.  Relaxed
   // layout only relaxes rules for vector alignment, not array alignment.
@@ -2046,7 +2213,7 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithVulkan1_1StillBad) {
   // Same as previous test, but with Vulkan 1.1, which includes
   // VK_KHR_relaxed_block_layout in core.
   std::string spirv = R"(
@@ -2087,6 +2254,75 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
+TEST_F(ValidateDecorations,
+       BlockArrayBaseAlignmentWithBlockStandardLayoutGood) {
+  // Same as previous test, but with VK_KHR_uniform_buffer_standard_layout
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpDecorate %_arr_float_uint_2 ArrayStride 16
+               OpDecorate %u DescriptorSet 0
+               OpDecorate %u Binding 0
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 8
+               OpDecorate %S Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_arr_float_uint_2 = OpTypeArray %float %uint_2
+          %S = OpTypeStruct %v2float %_arr_float_uint_2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %u = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetUniformBufferStandardLayout(getValidatorOptions(),
+                                                    true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, VulkanBufferBlockOnStorageBufferBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct BufferBlock
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In Vulkan, BufferBlock is disallowed on variables in "
+                        "the StorageBuffer storage class"));
+}
+
 TEST_F(ValidateDecorations, PushConstantArrayBaseAlignmentGood) {
   // Tests https://github.com/KhronosGroup/SPIRV-Tools/issues/1664
   // From GLSL vertex shader:
@@ -2289,10 +2525,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2300,7 +2536,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2327,10 +2563,10 @@
                 OpEntryPoint Vertex %1 "func1"
                 OpEntryPoint Fragment %2 "func2"
                 OpExecutionMode %2 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2338,7 +2574,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2369,10 +2605,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2380,7 +2616,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2401,10 +2637,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2412,7 +2648,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2446,10 +2682,10 @@
                 OpEntryPoint Vertex %1 "func1"
                 OpEntryPoint Fragment %2 "func2"
                 OpExecutionMode %2 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2457,10 +2693,10 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
- 
+
         %sub1 = OpFunction %void None %voidfn
   %label_sub1 = OpLabel
            %3 = OpAccessChain %ptr_float %pc1 %int_0
@@ -2500,10 +2736,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2511,7 +2747,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2826,8 +3062,8 @@
             OpMemoryModel Logical GLSL450
             OpEntryPoint Fragment %1 "main"
             OpExecutionMode %1 OriginUpperLeft
-
-            OpDecorate %struct BufferBlock
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
 
     %void = OpTypeVoid
   %voidfn = OpTypeFunction %void
@@ -3285,7 +3521,7 @@
       getDiagnosticString(),
       HasSubstr("Structure id 6 decorated as Block for variable in Uniform "
                 "storage class must follow standard uniform buffer layout "
-                "rules: member 2 at offset 24 is not aligned to 16"));
+                "rules: member 2 at offset 152 is not aligned to 16"));
 }
 
 TEST_F(ValidateDecorations,
@@ -3418,7 +3654,8 @@
       getDiagnosticString(),
       HasSubstr(
           "Structure id 6 decorated as Block for variable in Uniform storage "
-          "class must follow standard uniform buffer layout rules: member 4 is "
+          "class must follow standard uniform buffer layout rules: member 4 "
+          "contains "
           "an array with stride 49 not satisfying alignment to 16"));
 }
 
@@ -4317,41 +4554,6 @@
                 "pointer to a 16-bit floating-point scalar or vector object."));
 }
 
-TEST_F(ValidateDecorations, FPRoundingModeBadStorageClass) {
-  std::string spirv = R"(
-OpCapability Shader
-OpCapability Linkage
-OpCapability StorageBuffer16BitAccess
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-OpExtension "SPV_KHR_variable_pointers"
-OpExtension "SPV_KHR_16bit_storage"
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %main "main"
-OpDecorate %_ FPRoundingMode RTE
-%half = OpTypeFloat 16
-%float = OpTypeFloat 32
-%float_1_25 = OpConstant %float 1.25
-%half_ptr = OpTypePointer Private %half
-%half_ptr_var = OpVariable %half_ptr Private
-%void = OpTypeVoid
-%func = OpTypeFunction %void
-%main = OpFunction %void None %func
-%main_entry = OpLabel
-%_ = OpFConvert %half %float_1_25
-OpStore %half_ptr_var %_
-OpReturn
-OpFunctionEnd
-  )";
-
-  CompileSuccessfully(spirv);
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("FPRoundingMode decoration can be applied only to the "
-                        "Object operand of an OpStore in the StorageBuffer, "
-                        "PhysicalStorageBufferEXT, Uniform, "
-                        "PushConstant, Input, or Output Storage Classes."));
-}
-
 TEST_F(ValidateDecorations, FPRoundingModeMultipleOpStoreGood) {
   std::string spirv = R"(
 OpCapability Shader
@@ -4510,7 +4712,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-// Uniform decoration
+// Uniform and UniformId decorations
 
 TEST_F(ValidateDecorations, UniformDecorationGood) {
   const std::string spirv = R"(
@@ -4539,49 +4741,96 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
-TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
-  const std::string spirv = R"(
+// Returns SPIR-V assembly for a shader that uses a given decoration
+// instruction.
+std::string ShaderWithUniformLikeDecoration(const std::string& inst) {
+  return std::string(R"(
 OpCapability Shader
 OpMemoryModel Logical Simple
 OpEntryPoint GLCompute %main "main"
 OpExecutionMode %main LocalSize 1 1 1
-OpDecorate %fn Uniform
-%void = OpTypeVoid
-%fn = OpTypeFunction %void
-%main = OpFunction %void None %fn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  CompileSuccessfully(spirv);
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Uniform decoration applied to a non-object"));
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("%2 = OpTypeFunction %void"));
-}
-
-TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
-  const std::string spirv = R"(
-OpCapability Shader
-OpMemoryModel Logical Simple
-OpEntryPoint GLCompute %main "main"
-OpExecutionMode %main LocalSize 1 1 1
+OpName %subgroupscope "subgroupscope"
 OpName %call "call"
 OpName %myfunc "myfunc"
-OpDecorate %call Uniform
+OpName %int0 "int0"
+OpName %float0 "float0"
+OpName %fn "fn"
+)") + inst +
+         R"(
 %void = OpTypeVoid
-%fnty = OpTypeFunction %void
-%myfunc = OpFunction %void None %fnty
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 1
+%int0 = OpConstantNull %int
+%int_99 = OpConstant %int 99
+%subgroupscope = OpConstant %int 3
+%float0 = OpConstantNull %float
+%fn = OpTypeFunction %void
+%myfunc = OpFunction %void None %fn
 %myfuncentry = OpLabel
 OpReturn
 OpFunctionEnd
-%main = OpFunction %void None %fnty
+%main = OpFunction %void None %fn
 %entry = OpLabel
 %call = OpFunctionCall %void %myfunc
 OpReturn
 OpFunctionEnd
 )";
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13Bad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires SPIR-V version 1.4 or later\n"
+                        "  OpDecorateId %int0 UniformId %subgroupscope"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13BadTargetV14) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires SPIR-V version 1.4 or later"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV14Good) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorate %fn Uniform");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform decoration applied to a non-object"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationTargetsTypeBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %fn UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("UniformId decoration applied to a non-object"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorate %call Uniform");
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -4590,6 +4839,82 @@
                         "  %call = OpFunctionCall %void %myfunc"));
 }
 
+TEST_F(ValidateDecorations, UniformIdDecorationTargetsVoidValueBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %call UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4))
+      << spirv;
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("UniformId decoration applied to a value with void type\n"
+                "  %call = OpFunctionCall %void %myfunc"));
+}
+
+TEST_F(ValidateDecorations,
+       UniformDecorationWithScopeIdV14IdIsFloatValueIsBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %float0");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ConstantNull: expected Execution Scope to be a 32-bit int"));
+}
+
+TEST_F(ValidateDecorations,
+       UniformDecorationWithScopeIdV14IdIsInvalidIntValueBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int_99");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid scope value:\n %int_99 = OpConstant %int 99\n"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationWithScopeIdV14VulkanEnv) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int0");
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(": in Vulkan environment Execution Scope is limited to "
+                        "Workgroup and Subgroup"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationWithWrongInstructionBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 Uniform");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Decorations that don't take ID parameters may not be "
+                        "used with OpDecorateId\n"
+                        "  OpDecorateId %int0 Uniform"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithWrongInstructionBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorate %int0 UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Decorations taking ID parameters may not be used with OpDecorateId\n"
+          "  OpDecorate %int0 UniformId %subgroupscope"));
+}
+
 TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
   std::string spirv = R"(
             OpCapability Shader
@@ -4872,6 +5197,26 @@
                         "SPV_KHR_no_integer_wrap_decoration"));
 }
 
+TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionV13Bad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapOkInSPV14Good) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateDecorations, NoSignedWrapIAddGood) {
   std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
                                         "%val = OpIAdd %int %zero %zero");
@@ -4994,6 +5339,26 @@
                         "SPV_KHR_no_integer_wrap_decoration"));
 }
 
+TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionV13Bad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOkInSPV14Good) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateDecorations, NoUnsignedWrapIAddGood) {
   std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
                                         "%val = OpIAdd %int %zero %zero");
@@ -5082,6 +5447,37 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
+TEST_F(ValidateDecorations, AliasedandRestrictBad) {
+  const std::string body = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource GLSL 430
+OpMemberDecorate %Output 0 Offset 0
+OpDecorate %Output BufferBlock
+OpDecorate %dataOutput Restrict
+OpDecorate %dataOutput Aliased
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%Output = OpTypeStruct %float
+%_ptr_Uniform_Output = OpTypePointer Uniform %Output
+%dataOutput = OpVariable %_ptr_Uniform_Output Uniform
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decorated with both Aliased and Restrict is not allowed"));
+}
+
 // TODO(dneto): For NoUnsignedWrap and NoUnsignedWrap, permit
 // "OpExtInst for instruction numbers specified in the extended
 // instruction-set specifications as accepting this decoration."
@@ -5302,6 +5698,998 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
+TEST_F(ValidateDecorations, InvalidStraddle) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %inner_struct 0 Offset 0
+OpMemberDecorate %inner_struct 1 Offset 4
+OpDecorate %outer_struct Block
+OpMemberDecorate %outer_struct 0 Offset 0
+OpMemberDecorate %outer_struct 1 Offset 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%inner_struct = OpTypeStruct %float %float2
+%outer_struct = OpTypeStruct %float2 %inner_struct
+%ptr_ssbo_outer = OpTypePointer StorageBuffer %outer_struct
+%var = OpVariable %ptr_ssbo_outer StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow relaxed "
+                        "storage buffer layout rules: member 1 is an "
+                        "improperly straddling vector at offset 12"));
+}
+
+TEST_F(ValidateDecorations, DescriptorArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpMemberDecorate %struct 1 Offset 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%float2 = OpTypeVector %float 2
+%struct = OpTypeStruct %float %float2
+%struct_array = OpTypeArray %struct %int_2
+%ptr_ssbo_array = OpTypePointer StorageBuffer %struct_array
+%var = OpVariable %ptr_ssbo_array StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow standard "
+                        "storage buffer layout rules: member 1 at offset 1 is "
+                        "not aligned to 8"));
+}
+
+TEST_F(ValidateDecorations, DescriptorRuntimeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpMemberDecorate %struct 1 Offset 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%float2 = OpTypeVector %float 2
+%struct = OpTypeStruct %float %float2
+%struct_array = OpTypeRuntimeArray %struct
+%ptr_ssbo_array = OpTypePointer StorageBuffer %struct_array
+%var = OpVariable %ptr_ssbo_array StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow standard "
+                        "storage buffer layout rules: member 1 at offset 1 is "
+                        "not aligned to 8"));
+}
+
+TEST_F(ValidateDecorations, MultiDimensionalArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array_4 ArrayStride 4
+OpDecorate %array_3 ArrayStride 48
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%array_4 = OpTypeArray %int %int_4
+%array_3 = OpTypeArray %array_4 %int_3
+%struct = OpTypeStruct %array_3
+%ptr_struct = OpTypePointer Uniform %struct
+%var = OpVariable %ptr_struct Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "Uniform storage class must follow standard uniform "
+                        "buffer layout rules: member 0 contains an array with "
+                        "stride 4 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, ImproperStraddleInArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array ArrayStride 24
+OpMemberDecorate %inner 0 Offset 0
+OpMemberDecorate %inner 1 Offset 4
+OpMemberDecorate %inner 2 Offset 12
+OpMemberDecorate %inner 3 Offset 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%int2 = OpTypeVector %int 2
+%inner = OpTypeStruct %int %int2 %int %int
+%array = OpTypeArray %inner %int_2
+%struct = OpTypeStruct %array
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 4 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow relaxed "
+                        "storage buffer layout rules: member 1 is an "
+                        "improperly straddling vector at offset 28"));
+}
+
+TEST_F(ValidateDecorations, LargeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array ArrayStride 24
+OpMemberDecorate %inner 0 Offset 0
+OpMemberDecorate %inner 1 Offset 8
+OpMemberDecorate %inner 2 Offset 16
+OpMemberDecorate %inner 3 Offset 20
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_2000000 = OpConstant %int 2000000
+%int2 = OpTypeVector %int 2
+%inner = OpTypeStruct %int %int2 %int %int
+%array = OpTypeArray %inner %int_2000000
+%struct = OpTypeStruct %array
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+// NonWritable
+
+// Returns a SPIR-V shader module with variables in various storage classes,
+// parameterizable by which ID should be decorated as NonWritable.
+std::string ShaderWithNonWritableTarget(const std::string& target,
+                                        bool member_decorate = false) {
+  const std::string decoration_inst =
+      std::string(member_decorate ? "OpMemberDecorate " : "OpDecorate ") +
+      target + (member_decorate ? " 0" : "");
+
+  return std::string(R"(
+            OpCapability Shader
+            OpCapability RuntimeDescriptorArrayEXT
+            OpExtension "SPV_EXT_descriptor_indexing"
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Vertex %main "main"
+            OpName %label "label"
+            OpName %param_f "param_f"
+            OpName %param_p "param_p"
+            OpName %_ptr_imstor "_ptr_imstor"
+            OpName %_ptr_imsam "_ptr_imsam"
+            OpName %var_wg "var_wg"
+            OpName %var_imsam "var_imsam"
+            OpName %var_priv "var_priv"
+            OpName %var_func "var_func"
+            OpName %simple_struct "simple_struct"
+
+            OpDecorate %struct_b Block
+            OpDecorate %struct_b_rtarr Block
+            OpMemberDecorate %struct_b 0 Offset 0
+            OpMemberDecorate %struct_b_rtarr 0 Offset 0
+            OpDecorate %rtarr ArrayStride 4
+)") + decoration_inst +
+
+         R"( NonWritable
+
+      %void = OpTypeVoid
+   %void_fn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+   %float_0 = OpConstant %float 0
+   %int     = OpTypeInt 32 0
+   %int_2   = OpConstant %int 2
+  %struct_b = OpTypeStruct %float
+ %rtarr = OpTypeRuntimeArray %float
+%struct_b_rtarr = OpTypeStruct %rtarr
+%simple_struct = OpTypeStruct %float
+ ; storage image
+ %imstor = OpTypeImage %float 2D 0 0 0 2 R32f
+ ; sampled image
+ %imsam = OpTypeImage %float 2D 0 0 0 1 R32f
+%array_imstor = OpTypeArray %imstor %int_2
+%rta_imstor = OpTypeRuntimeArray %imstor
+
+%_ptr_Uniform_stb        = OpTypePointer Uniform %struct_b
+%_ptr_StorageBuffer_stb  = OpTypePointer StorageBuffer %struct_b
+%_ptr_StorageBuffer_stb_rtarr  = OpTypePointer StorageBuffer %struct_b_rtarr
+%_ptr_Workgroup          = OpTypePointer Workgroup %float
+%_ptr_Private            = OpTypePointer Private %float
+%_ptr_Function           = OpTypePointer Function %float
+%_ptr_imstor             = OpTypePointer UniformConstant %imstor
+%_ptr_imsam              = OpTypePointer UniformConstant %imsam
+%_ptr_array_imstor       = OpTypePointer UniformConstant %array_imstor
+%_ptr_rta_imstor         = OpTypePointer UniformConstant %rta_imstor
+
+%extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
+
+%var_ubo = OpVariable %_ptr_Uniform_stb Uniform
+%var_ssbo_sb = OpVariable %_ptr_StorageBuffer_stb StorageBuffer
+%var_ssbo_sb_rtarr = OpVariable %_ptr_StorageBuffer_stb_rtarr StorageBuffer
+%var_wg = OpVariable %_ptr_Workgroup Workgroup
+%var_priv = OpVariable %_ptr_Private Private
+%var_imstor = OpVariable %_ptr_imstor UniformConstant
+%var_imsam = OpVariable %_ptr_imsam UniformConstant
+%var_array_imstor = OpVariable %_ptr_array_imstor UniformConstant
+%var_rta_imstor = OpVariable %_ptr_rta_imstor UniformConstant
+
+  %helper = OpFunction %void None %extra_fn
+ %param_f = OpFunctionParameter %float
+ %param_p = OpFunctionParameter %_ptr_Private
+ %param_pimstor = OpFunctionParameter %_ptr_imstor
+%helper_label = OpLabel
+%helper_func_var = OpVariable %_ptr_Function Function
+            OpReturn
+            OpFunctionEnd
+
+    %main = OpFunction %void None %void_fn
+   %label = OpLabel
+%var_func = OpVariable %_ptr_Function Function
+            OpReturn
+            OpFunctionEnd
+)";
+}
+
+TEST_F(ValidateDecorations, NonWritableLabelTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%label");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %label = OpLabel"));
+}
+
+TEST_F(ValidateDecorations, NonWritableTypeTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%void");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %void = OpTypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%float_0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %float_0 = OpConstant %float 0"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueParamBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_f");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %param_f = OpFunctionParameter %float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamButWrongTypeBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_p");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Target of NonWritable decoration is invalid: must "
+          "point to a storage image, uniform block, or storage "
+          "buffer\n  %param_p = OpFunctionParameter %_ptr_Private_float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamStorageImageGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_pimstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarStorageImageGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSampledImageBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_imsam");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_imsam"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarUboGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_ubo");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInUniformGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %struct_bb BufferBlock
+OpMemberDecorate %struct_bb 0 Offset 0
+OpDecorate %var_ssbo_u NonWritable
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%struct_bb = OpTypeStruct %float
+%_ptr_Uniform_stbb       = OpTypePointer Uniform %struct_bb
+%var_ssbo_u = OpVariable %_ptr_Uniform_stbb Uniform
+%main = OpFunction %void None %void_fn
+%label = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInStorageBufferGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_sb");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfSsboInStorageBufferGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%struct_b_rtarr", true);
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfStructGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%simple_struct", true);
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, NonWritableVarWorkgroupBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_wg");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_wg"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarWorkgroupV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_wg");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, storage "
+                        "buffer, or variable in Private or Function storage "
+                        "class\n  %var_wg"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV13Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV14Good) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV13TargetV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
+TEST_F(ValidateDecorations, NonWritableArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_array_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, NonWritableRuntimeArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_rta_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateWebGPUCombineDecorationResult, Decorate) {
+  const char* const decoration = std::get<0>(GetParam());
+  const TestResult& test_result = std::get<1>(GetParam());
+
+  CodeGenerator generator = CodeGenerator::GetWebGPUShaderCodeGenerator();
+  generator.before_types_ = "OpDecorate %u32 ";
+  generator.before_types_ += decoration;
+  generator.before_types_ += "\n";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "Vertex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str != "") {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+}
+
+TEST_P(ValidateWebGPUCombineDecorationResult, DecorateMember) {
+  const char* const decoration = std::get<0>(GetParam());
+  const TestResult& test_result = std::get<1>(GetParam());
+
+  CodeGenerator generator = CodeGenerator::GetWebGPUShaderCodeGenerator();
+  generator.before_types_ = "OpMemberDecorate %struct_type 0 ";
+  generator.before_types_ += decoration;
+  generator.before_types_ += "\n";
+
+  generator.after_types_ = "%struct_type = OpTypeStruct %u32\n";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "Vertex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (!test_result.error_str.empty()) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    DecorationCapabilityFailure, ValidateWebGPUCombineDecorationResult,
+    Combine(Values("CPacked", "Patch", "Sample", "Constant",
+                   "SaturatedConversion", "NonUniformEXT"),
+            Values(TestResult(SPV_ERROR_INVALID_CAPABILITY,
+                              "requires one of these capabilities"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DecorationWhitelistFailure, ValidateWebGPUCombineDecorationResult,
+    Combine(Values("RelaxedPrecision", "BufferBlock", "GLSLShared",
+                   "GLSLPacked", "Invariant", "Volatile", "Coherent"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_ID,
+                "is not valid for the WebGPU execution environment."))));
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV13Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV14Good) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV13TargetV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
+TEST_F(ValidateDecorations, BufferBlockV13ValV14Good) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 BufferBlock
+%1 = OpTypeStruct
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, BufferBlockV14Bad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 BufferBlock
+%1 = OpTypeStruct
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("2nd operand of Decorate: operand BufferBlock(3) "
+                        "requires SPIR-V version 1.3 or earlier"));
+}
+
+// Component
+
+TEST_F(ValidateDecorations, ComponentDecorationBadTarget) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %t Component 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%t = OpTypeVector %float 2
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of Component decoration must be "
+                        "a memory object declaration"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationBadStorageClass) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %v Component 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%t = OpTypeVector %float 2
+%ptr_private = OpTypePointer Private %t
+%v = OpVariable %ptr_private Private
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of Component decoration is invalid: must "
+                        "point to a Storage Class of Input(1) or Output(3)"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationBadTypeVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Matrix
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %v Component 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%vtype = OpTypeVector %float 4
+%t = OpTypeMatrix %vtype 4
+%ptr_input = OpTypePointer Input %t
+%v = OpVariable %ptr_input Input
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component decoration specified for type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a scalar or vector"));
+}
+
+std::string ShaderWithComponentDecoration(const std::string& type,
+                                          const std::string& decoration) {
+  return R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %entryPointOutput
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %entryPointOutput Location 0
+OpDecorate %entryPointOutput )" +
+         decoration + R"(
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v3float = OpTypeVector %float 3
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_2 = OpConstant %uint 2
+%arr_v3float_uint_2 = OpTypeArray %v3float %uint_2
+%float_0 = OpConstant %float 0
+%_ptr_Output_type = OpTypePointer Output %)" + type + R"(
+%entryPointOutput = OpVariable %_ptr_Output_type Output
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationIntGood0Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("uint", "Component 0");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationIntGood1Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("uint", "Component 1");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationIntGood2Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("uint", "Component 2");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationIntGood3Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("uint", "Component 3");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationIntBad4Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("uint", "Component 4");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sequence of components starting with 4 "
+                        "and ending with 4 gets larger than 3"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationVector3GoodVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("v3float", "Component 1");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationVector4GoodVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("v4float", "Component 0");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationVector4Bad1Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("v4float", "Component 1");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sequence of components starting with 1 "
+                        "and ending with 4 gets larger than 3"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationVector4Bad3Vulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = ShaderWithComponentDecoration("v4float", "Component 3");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sequence of components starting with 3 "
+                        "and ending with 6 gets larger than 3"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationArrayGoodVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv =
+      ShaderWithComponentDecoration("arr_v3float_uint_2", "Component 1");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationArrayBadVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv =
+      ShaderWithComponentDecoration("arr_v3float_uint_2", "Component 2");
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sequence of components starting with 2 "
+                        "and ending with 4 gets larger than 3"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationBlockGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main" %9 %12
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %9 Location 0
+OpMemberDecorate %block 0 Location 2
+OpMemberDecorate %block 0 Component 1
+OpDecorate %block Block
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%float = OpTypeFloat 32
+%vec3 = OpTypeVector %float 3
+%8 = OpTypePointer Output %vec3
+%9 = OpVariable %8 Output
+%block = OpTypeStruct %vec3
+%11 = OpTypePointer Input %block
+%12 = OpVariable %11 Input
+%int = OpTypeInt 32 1
+%14 = OpConstant %int 0
+%15 = OpTypePointer Input %vec3
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%16 = OpAccessChain %15 %12 %14
+%17 = OpLoad %vec3 %16
+OpStore %9 %17
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationBlockBadVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main" %9 %12
+OpExecutionMode %4 OriginUpperLeft
+OpDecorate %9 Location 0
+OpMemberDecorate %block 0 Location 2
+OpMemberDecorate %block 0 Component 2
+OpDecorate %block Block
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%float = OpTypeFloat 32
+%vec3 = OpTypeVector %float 3
+%8 = OpTypePointer Output %vec3
+%9 = OpVariable %8 Output
+%block = OpTypeStruct %vec3
+%11 = OpTypePointer Input %block
+%12 = OpVariable %11 Input
+%int = OpTypeInt 32 1
+%14 = OpConstant %int 0
+%15 = OpTypePointer Input %vec3
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%16 = OpAccessChain %15 %12 %14
+%17 = OpLoad %vec3 %16
+OpStore %9 %17
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sequence of components starting with 2 "
+                        "and ending with 4 gets larger than 3"));
+}
+
+TEST_F(ValidateDecorations, ComponentDecorationFunctionParameter) {
+  std::string spirv = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Vertex %main "main"
+
+              OpDecorate %param_f Component 0
+
+      %void = OpTypeVoid
+   %void_fn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+   %float_0 = OpConstant %float 0
+   %int     = OpTypeInt 32 0
+   %int_2   = OpConstant %int 2
+  %struct_b = OpTypeStruct %float
+
+%extra_fn = OpTypeFunction %void %float
+
+  %helper = OpFunction %void None %extra_fn
+ %param_f = OpFunctionParameter %float
+%helper_label = OpLabel
+            OpReturn
+            OpFunctionEnd
+
+    %main = OpFunction %void None %void_fn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp
index 480042a..dac95f6 100644
--- a/test/val/val_derivatives_test.cpp
+++ b/test/val/val_derivatives_test.cpp
@@ -145,10 +145,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Derivative instructions require Fragment execution model: DPdx"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Derivative instructions require Fragment or GLCompute "
+                        "execution model: DPdx"));
 }
 
 }  // namespace
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index d1505da..73cb48f 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -428,6 +428,10 @@
 %f16vec8_input = OpVariable %f16vec8_ptr_input Input
 %f16_ptr_input = OpTypePointer Input %f16
 
+%u32vec8_ptr_input = OpTypePointer Input %u32vec8
+%u32vec8_input = OpVariable %u32vec8_ptr_input Input
+%u32_ptr_input = OpTypePointer Input %u32
+
 %f32_ptr_generic = OpTypePointer Generic %f32
 %u32_ptr_generic = OpTypePointer Generic %u32
 
@@ -493,20 +497,20 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSqrtLike, ValidateGlslStd450SqrtLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Round",
-                            "RoundEven",
-                            "FAbs",
-                            "Trunc",
-                            "FSign",
-                            "Floor",
-                            "Ceil",
-                            "Fract",
-                            "Sqrt",
-                            "InverseSqrt",
-                            "Normalize",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSqrtLike, ValidateGlslStd450SqrtLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Round",
+                             "RoundEven",
+                             "FAbs",
+                             "Trunc",
+                             "FSign",
+                             "Floor",
+                             "Ceil",
+                             "Fract",
+                             "Sqrt",
+                             "InverseSqrt",
+                             "Normalize",
+                         }));
 
 TEST_P(ValidateGlslStd450FMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -560,15 +564,15 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateGlslStd450FMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "FMin",
-                            "FMax",
-                            "Step",
-                            "Reflect",
-                            "NMin",
-                            "NMax",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFMinLike, ValidateGlslStd450FMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "FMin",
+                             "FMax",
+                             "Step",
+                             "Reflect",
+                             "NMin",
+                             "NMax",
+                         }));
 
 TEST_P(ValidateGlslStd450FClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -635,15 +639,15 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateGlslStd450FClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "FClamp",
-                            "FMix",
-                            "SmoothStep",
-                            "Fma",
-                            "FaceForward",
-                            "NClamp",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFClampLike, ValidateGlslStd450FClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "FClamp",
+                             "FMix",
+                             "SmoothStep",
+                             "Fma",
+                             "FaceForward",
+                             "NClamp",
+                         }));
 
 TEST_P(ValidateGlslStd450SAbsLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -716,14 +720,14 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateGlslStd450SAbsLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "SAbs",
-                            "SSign",
-                            "FindILsb",
-                            "FindUMsb",
-                            "FindSMsb",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSAbsLike, ValidateGlslStd450SAbsLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "SAbs",
+                             "SSign",
+                             "FindILsb",
+                             "FindUMsb",
+                             "FindSMsb",
+                         }));
 
 TEST_F(ValidateExtInst, FindUMsbNot32Bit) {
   const std::string body = R"(
@@ -865,13 +869,13 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateGlslStd450UMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UMin",
-                            "SMin",
-                            "UMax",
-                            "SMax",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMinLike, ValidateGlslStd450UMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UMin",
+                             "SMin",
+                             "UMax",
+                             "SMax",
+                         }));
 
 TEST_P(ValidateGlslStd450UClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1028,11 +1032,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateGlslStd450UClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UClamp",
-                            "SClamp",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUClampLike, ValidateGlslStd450UClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UClamp",
+                             "SClamp",
+                         }));
 
 TEST_P(ValidateGlslStd450SinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1083,27 +1087,27 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSinLike, ValidateGlslStd450SinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Radians",
-                            "Degrees",
-                            "Sin",
-                            "Cos",
-                            "Tan",
-                            "Asin",
-                            "Acos",
-                            "Atan",
-                            "Sinh",
-                            "Cosh",
-                            "Tanh",
-                            "Asinh",
-                            "Acosh",
-                            "Atanh",
-                            "Exp",
-                            "Exp2",
-                            "Log",
-                            "Log2",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSinLike, ValidateGlslStd450SinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Radians",
+                             "Degrees",
+                             "Sin",
+                             "Cos",
+                             "Tan",
+                             "Asin",
+                             "Acos",
+                             "Atan",
+                             "Sinh",
+                             "Cosh",
+                             "Tanh",
+                             "Asinh",
+                             "Acosh",
+                             "Atanh",
+                             "Exp",
+                             "Exp2",
+                             "Log",
+                             "Log2",
+                         }));
 
 TEST_P(ValidateGlslStd450PowLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1168,11 +1172,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllPowLike, ValidateGlslStd450PowLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Atan2",
-                            "Pow",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllPowLike, ValidateGlslStd450PowLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Atan2",
+                             "Pow",
+                         }));
 
 TEST_F(ValidateExtInst, GlslStd450DeterminantSuccess) {
   const std::string body = R"(
@@ -1795,14 +1799,14 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str()));
 }
 
-INSTANTIATE_TEST_CASE_P(AllPack, ValidateGlslStd450Pack,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "PackSnorm4x8",
-                            "PackUnorm4x8",
-                            "PackSnorm2x16",
-                            "PackUnorm2x16",
-                            "PackHalf2x16",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllPack, ValidateGlslStd450Pack,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "PackSnorm4x8",
+                             "PackUnorm4x8",
+                             "PackSnorm2x16",
+                             "PackUnorm2x16",
+                             "PackHalf2x16",
+                         }));
 
 TEST_F(ValidateExtInst, PackDouble2x32Success) {
   const std::string body = R"(
@@ -2034,14 +2038,14 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str()));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUnpack, ValidateGlslStd450Unpack,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UnpackSnorm4x8",
-                            "UnpackUnorm4x8",
-                            "UnpackSnorm2x16",
-                            "UnpackUnorm2x16",
-                            "UnpackHalf2x16",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUnpack, ValidateGlslStd450Unpack,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UnpackSnorm4x8",
+                             "UnpackUnorm4x8",
+                             "UnpackSnorm2x16",
+                             "UnpackUnorm2x16",
+                             "UnpackHalf2x16",
+                         }));
 
 TEST_F(ValidateExtInst, UnpackDouble2x32Success) {
   const std::string body = R"(
@@ -2831,7 +2835,7 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllSqrtLike, ValidateOpenCLStdSqrtLike,
     ::testing::ValuesIn(std::vector<std::string>{
         "acos",         "acosh",       "acospi",       "asin",
@@ -2851,7 +2855,7 @@
         "native_log",   "native_log2", "native_log10", "native_recip",
         "native_rsqrt", "native_sin",  "native_sqrt",  "native_tan",
         "degrees",      "radians",     "sign",
-    }), );
+    }));
 
 TEST_P(ValidateOpenCLStdFMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -2905,16 +2909,16 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateOpenCLStdFMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "atan2",     "atan2pi",       "copysign",
-                            "fdim",      "fmax",          "fmin",
-                            "fmod",      "maxmag",        "minmag",
-                            "hypot",     "nextafter",     "pow",
-                            "powr",      "remainder",     "half_divide",
-                            "half_powr", "native_divide", "native_powr",
-                            "step",      "fmax_common",   "fmin_common",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFMinLike, ValidateOpenCLStdFMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "atan2",     "atan2pi",       "copysign",
+                             "fdim",      "fmax",          "fmin",
+                             "fmod",      "maxmag",        "minmag",
+                             "hypot",     "nextafter",     "pow",
+                             "powr",      "remainder",     "half_divide",
+                             "half_powr", "native_divide", "native_powr",
+                             "step",      "fmax_common",   "fmin_common",
+                         }));
 
 TEST_P(ValidateOpenCLStdFClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -2981,14 +2985,14 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateOpenCLStdFClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "fma",
-                            "mad",
-                            "fclamp",
-                            "mix",
-                            "smoothstep",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFClampLike, ValidateOpenCLStdFClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "fma",
+                             "mad",
+                             "fclamp",
+                             "mix",
+                             "smoothstep",
+                         }));
 
 TEST_P(ValidateOpenCLStdSAbsLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3048,14 +3052,14 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateOpenCLStdSAbsLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_abs",
-                            "clz",
-                            "ctz",
-                            "popcount",
-                            "u_abs",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSAbsLike, ValidateOpenCLStdSAbsLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_abs",
+                             "clz",
+                             "ctz",
+                             "popcount",
+                             "u_abs",
+                         }));
 
 TEST_P(ValidateOpenCLStdUMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3147,26 +3151,26 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateOpenCLStdUMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_max",
-                            "u_max",
-                            "s_min",
-                            "u_min",
-                            "s_abs_diff",
-                            "s_add_sat",
-                            "u_add_sat",
-                            "s_mul_hi",
-                            "rotate",
-                            "s_sub_sat",
-                            "u_sub_sat",
-                            "s_hadd",
-                            "u_hadd",
-                            "s_rhadd",
-                            "u_rhadd",
-                            "u_abs_diff",
-                            "u_mul_hi",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMinLike, ValidateOpenCLStdUMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_max",
+                             "u_max",
+                             "s_min",
+                             "u_min",
+                             "s_abs_diff",
+                             "s_add_sat",
+                             "u_add_sat",
+                             "s_mul_hi",
+                             "rotate",
+                             "s_sub_sat",
+                             "u_sub_sat",
+                             "s_hadd",
+                             "u_hadd",
+                             "s_rhadd",
+                             "u_rhadd",
+                             "u_abs_diff",
+                             "u_mul_hi",
+                         }));
 
 TEST_P(ValidateOpenCLStdUClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3284,15 +3288,15 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateOpenCLStdUClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_clamp",
-                            "u_clamp",
-                            "s_mad_hi",
-                            "u_mad_sat",
-                            "s_mad_sat",
-                            "u_mad_hi",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUClampLike, ValidateOpenCLStdUClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_clamp",
+                             "u_clamp",
+                             "s_mad_hi",
+                             "u_mad_sat",
+                             "s_mad_sat",
+                             "u_mad_hi",
+                         }));
 
 // -------------------------------------------------------------
 TEST_P(ValidateOpenCLStdUMul24Like, Success) {
@@ -3398,11 +3402,11 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMul24Like, ValidateOpenCLStdUMul24Like,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_mul24",
-                            "u_mul24",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMul24Like, ValidateOpenCLStdUMul24Like,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_mul24",
+                             "u_mul24",
+                         }));
 
 TEST_P(ValidateOpenCLStdUMad24Like, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3533,11 +3537,11 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMad24Like, ValidateOpenCLStdUMad24Like,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_mad24",
-                            "u_mad24",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMad24Like, ValidateOpenCLStdUMad24Like,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_mad24",
+                             "u_mad24",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdCrossSuccess) {
   const std::string body = R"(
@@ -3662,11 +3666,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllLengthLike, ValidateOpenCLStdLengthLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "length",
-                            "fast_length",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllLengthLike, ValidateOpenCLStdLengthLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "length",
+                             "fast_length",
+                         }));
 
 TEST_P(ValidateOpenCLStdDistanceLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3751,11 +3755,11 @@
                         "expected operands P0 and P1 to be of the same type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllDistanceLike, ValidateOpenCLStdDistanceLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "distance",
-                            "fast_distance",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllDistanceLike, ValidateOpenCLStdDistanceLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "distance",
+                             "fast_distance",
+                         }));
 
 TEST_P(ValidateOpenCLStdNormalizeLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3811,11 +3815,11 @@
                         "expected operand P type to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllNormalizeLike, ValidateOpenCLStdNormalizeLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "normalize",
-                            "fast_normalize",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllNormalizeLike, ValidateOpenCLStdNormalizeLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "normalize",
+                             "fast_normalize",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdBitselectSuccess) {
   const std::string body = R"(
@@ -4208,15 +4212,15 @@
                 ": expected operand P data type to be 16-bit float scalar"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllVStoreHalfLike, ValidateOpenCLStdVStoreHalfLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "vstore_half",
-                            "vstore_half_r",
-                            "vstore_halfn",
-                            "vstore_halfn_r",
-                            "vstorea_halfn",
-                            "vstorea_halfn_r",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllVStoreHalfLike, ValidateOpenCLStdVStoreHalfLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "vstore_half",
+                             "vstore_half_r",
+                             "vstore_halfn",
+                             "vstore_halfn_r",
+                             "vstorea_halfn",
+                             "vstorea_halfn_r",
+                         }));
 
 TEST_P(ValidateOpenCLStdVLoadHalfLike, SuccessPhysical32) {
   const std::string ext_inst_name = GetParam();
@@ -4375,11 +4379,11 @@
                         "components of Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllVLoadHalfLike, ValidateOpenCLStdVLoadHalfLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "vload_halfn",
-                            "vloada_halfn",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllVLoadHalfLike, ValidateOpenCLStdVLoadHalfLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "vload_halfn",
+                             "vloada_halfn",
+                         }));
 
 TEST_F(ValidateExtInst, VLoadNSuccessFloatPhysical32) {
   std::ostringstream ss;
@@ -4485,14 +4489,15 @@
 
 TEST_F(ValidateExtInst, VLoadNWrongStorageClass) {
   std::ostringstream ss;
-  ss << "%ptr = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n";
+  ss << "%ptr = OpAccessChain %u32_ptr_input %u32vec8_input %u32_1\n";
   ss << "%val1 = OpExtInst %u32vec2 %extinst vloadn %u32_1 %ptr 2\n";
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpenCL.std vloadn: expected operand P storage class "
-                        "to be UniformConstant or Generic"));
+                        "to be UniformConstant, Generic, CrossWorkgroup, "
+                        "Workgroup or Function"));
 }
 
 TEST_F(ValidateExtInst, VLoadNWrongComponentType) {
@@ -4746,19 +4751,21 @@
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 124[%_ptr_Generic_float] cannot be a type"));
+              HasSubstr("Operand 127[%_ptr_Generic_float] cannot be a type"));
 }
 
-TEST_F(ValidateExtInst, VStoreNPNotGeneric) {
+TEST_F(ValidateExtInst, VStoreNWrongStorageClass) {
   std::ostringstream ss;
-  ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n";
+  ss << "%ptr_w = OpAccessChain %f32_ptr_uniform_constant "
+        "%f32vec8_uniform_constant %u32_1\n";
   ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 %ptr_w\n";
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpenCL.std vstoren: expected operand P storage class "
-                        "to be Generic"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpenCL.std vstoren: expected operand P storage class "
+                "to be Generic, CrossWorkgroup, Workgroup or Function"));
 }
 
 TEST_F(ValidateExtInst, VStorePWrongDataType) {
@@ -5060,7 +5067,7 @@
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 134[%_ptr_UniformConstant_uchar] cannot be a "
+              HasSubstr("Operand 137[%_ptr_UniformConstant_uchar] cannot be a "
                         "type"));
 }
 
@@ -5299,12 +5306,12 @@
           ": expected data type of the pointer to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFractLike, ValidateOpenCLStdFractLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "fract",
-                            "modf",
-                            "sincos",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFractLike, ValidateOpenCLStdFractLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "fract",
+                             "modf",
+                             "sincos",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdRemquoSuccess) {
   const std::string body = R"(
@@ -5518,11 +5525,11 @@
                         "number of components as Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFrexpLike, ValidateOpenCLStdFrexpLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "frexp",
-                            "lgamma_r",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFrexpLike, ValidateOpenCLStdFrexpLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "frexp",
+                             "lgamma_r",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdIlogbSuccess) {
   const std::string body = R"(
@@ -5716,12 +5723,12 @@
                         "components as Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllLdexpLike, ValidateOpenCLStdLdexpLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "ldexp",
-                            "pown",
-                            "rootn",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllLdexpLike, ValidateOpenCLStdLdexpLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "ldexp",
+                             "pown",
+                             "rootn",
+                         }));
 
 TEST_P(ValidateOpenCLStdUpsampleLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -5808,11 +5815,11 @@
                 "be half of the bit width of components of Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "u_upsample",
-                            "s_upsample",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "u_upsample",
+                             "s_upsample",
+                         }));
 
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_extensions_test.cpp b/test/val/val_extensions_test.cpp
index 3d6466d..682c321 100644
--- a/test/val/val_extensions_test.cpp
+++ b/test/val/val_extensions_test.cpp
@@ -43,7 +43,7 @@
   return "Found unrecognized extension " + extension;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExpectSuccess, ValidateKnownExtensions,
     Values(
         // Match the order as published on the SPIR-V Registry.
@@ -64,9 +64,9 @@
         "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1",
         "SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing"));
 
-INSTANTIATE_TEST_CASE_P(FailSilently, ValidateUnknownExtensions,
-                        Values("ERROR_unknown_extension", "SPV_KHR_",
-                               "SPV_KHR_shader_ballot_ERROR"));
+INSTANTIATE_TEST_SUITE_P(FailSilently, ValidateUnknownExtensions,
+                         Values("ERROR_unknown_extension", "SPV_KHR_",
+                                "SPV_KHR_shader_ballot_ERROR"));
 
 TEST_P(ValidateKnownExtensions, ExpectSuccess) {
   const std::string extension = GetParam();
@@ -227,8 +227,8 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
 }
 
-INSTANTIATE_TEST_CASE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities,
-                        ValuesIn(AMDShaderBallotGroupInstructions()));
+INSTANTIATE_TEST_SUITE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities,
+                         ValuesIn(AMDShaderBallotGroupInstructions()));
 
 TEST_P(ValidateAMDShaderBallotCapabilities, ExpectFailure) {
   // Fail because the module does not specify the SPV_AMD_shader_ballot
@@ -251,8 +251,8 @@
                             " requires one of these capabilities: Groups")));
 }
 
-INSTANTIATE_TEST_CASE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities,
-                        ValuesIn(AMDShaderBallotGroupInstructions()));
+INSTANTIATE_TEST_SUITE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities,
+                         ValuesIn(AMDShaderBallotGroupInstructions()));
 
 struct ExtIntoCoreCase {
   const char* ext;
@@ -288,9 +288,12 @@
 
   CompileSuccessfully(code.c_str(), GetParam().env);
   if (GetParam().success) {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env));
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env))
+        << getDiagnosticString();
   } else {
-    ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env));
+    ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env))
+        << " in " << spvTargetEnvDescription(GetParam().env) << ":\n"
+        << code;
     const std::string message = getDiagnosticString();
     if (spvIsVulkanEnv(GetParam().env)) {
       EXPECT_THAT(message, HasSubstr(std::string(GetParam().cap) +
@@ -305,7 +308,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KHR_extensions, ValidateExtIntoCore,
     ValuesIn(std::vector<ExtIntoCoreCase>{
         // SPV_KHR_shader_draw_parameters became core SPIR-V 1.3
diff --git a/test/val/val_fixtures.h b/test/val/val_fixtures.h
index 69bc9d8..5635c78 100644
--- a/test/val/val_fixtures.h
+++ b/test/val/val_fixtures.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "source/val/validation_state.h"
+#include "spirv-tools/libspirv.h"
 #include "test/test_fixture.h"
 #include "test/unit_spirv.h"
 
@@ -37,6 +38,11 @@
   // Returns the a spv_const_binary struct
   spv_const_binary get_const_binary();
 
+  // Assembles the given SPIR-V text, checks that it fails to assemble,
+  // and returns resulting diagnostic.  No internal state is updated.
+  std::string CompileFailure(std::string code,
+                             spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
+
   // Checks that 'code' is valid SPIR-V text representation and stores the
   // binary version for further method calls.
   void CompileSuccessfully(std::string code,
@@ -101,16 +107,30 @@
 }
 
 template <typename T>
+std::string ValidateBase<T>::CompileFailure(std::string code,
+                                            spv_target_env env) {
+  spv_diagnostic diagnostic = nullptr;
+  EXPECT_NE(SPV_SUCCESS,
+            spvTextToBinary(ScopedContext(env).context, code.c_str(),
+                            code.size(), &binary_, &diagnostic));
+  std::string result(diagnostic->error);
+  spvDiagnosticDestroy(diagnostic);
+  return result;
+}
+
+template <typename T>
 void ValidateBase<T>::CompileSuccessfully(std::string code,
                                           spv_target_env env) {
   DestroyBinary();
   spv_diagnostic diagnostic = nullptr;
-  ASSERT_EQ(SPV_SUCCESS,
-            spvTextToBinary(ScopedContext(env).context, code.c_str(),
-                            code.size(), &binary_, &diagnostic))
+  ScopedContext context(env);
+  auto status = spvTextToBinary(context.context, code.c_str(), code.size(),
+                                &binary_, &diagnostic);
+  EXPECT_EQ(SPV_SUCCESS, status)
       << "ERROR: " << diagnostic->error
       << "\nSPIR-V could not be compiled into binary:\n"
       << code;
+  ASSERT_EQ(SPV_SUCCESS, status);
   spvDiagnosticDestroy(diagnostic);
 }
 
@@ -125,6 +145,13 @@
 template <typename T>
 spv_result_t ValidateBase<T>::ValidateInstructions(spv_target_env env) {
   DestroyDiagnostic();
+  if (binary_ == nullptr) {
+    fprintf(stderr,
+            "ERROR: Attempting to validate a null binary, did you forget to "
+            "call CompileSuccessfully?");
+    fflush(stderr);
+  }
+  assert(binary_ != nullptr);
   return spvValidateWithOptions(ScopedContext(env).context, options_,
                                 get_const_binary(), &diagnostic_);
 }
diff --git a/test/val/val_function_test.cpp b/test/val/val_function_test.cpp
new file mode 100644
index 0000000..af0199a
--- /dev/null
+++ b/test/val/val_function_test.cpp
@@ -0,0 +1,843 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateFunctionCall = spvtest::ValidateBase<std::string>;
+
+std::string GenerateShader(const std::string& storage_class,
+                           const std::string& capabilities,
+                           const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%call = OpFunctionCall %void %callee %var
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderParameter(const std::string& storage_class,
+                                    const std::string& capabilities,
+                                    const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %p "p"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%func_ty = OpTypeFunction %void %ptr
+%caller = OpFunction %void None %func_ty
+%p = OpFunctionParameter %ptr
+%1 = OpLabel
+%call = OpFunctionCall %void %callee %p
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderAccessChain(const std::string& storage_class,
+                                      const std::string& capabilities,
+                                      const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+OpName %gep "gep"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int2 = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%ptr = OpTypePointer )" + storage_class + R"( %int2
+%ptr2 = OpTypePointer )" +
+                      storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr2
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%gep = OpAccessChain %ptr2 %var %int_0
+%call = OpFunctionCall %void %callee %gep
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+TEST_P(ValidateFunctionCall, VariableNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%var] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointersStorageClass) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShader(storage_class, "OpCapability VariablePointers",
+                     "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%p] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderParameter(storage_class, "OpCapability VariablePointers",
+                              "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  spv_result_t expected_result =
+      storage_class == "UniformConstant" ? SPV_SUCCESS : SPV_ERROR_INVALID_ID;
+  EXPECT_EQ(expected_result, ValidateInstructions());
+  if (valid_sc) {
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "Pointer operand 2[%gep] must be a memory object declaration"));
+  } else {
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 2[%gep] requires a "
+                            "variable pointers capability"));
+    } else if (storage_class != "UniformConstant") {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall,
+       NonMemoryObjectDeclarationVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate =
+      storage_class == "StorageBuffer" || storage_class == "UniformConstant";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderAccessChain(storage_class, "OpCapability VariablePointers",
+                                "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate = storage_class == "StorageBuffer" ||
+                  storage_class == "Workgroup" ||
+                  storage_class == "UniformConstant";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMatchingPointers) {
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+ %_struct_15 = OpTypeStruct %int
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+         %18 = OpTypeFunction %void %_ptr_Function__struct_15
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %14
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
+         %21 = OpFunctionCall %void %22 %20
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %void None %18
+         %23 = OpFunctionParameter %_ptr_Function__struct_15
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMatchingPointersNestedStruct) {
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpDecorate %_runtimearr__struct_4 ArrayStride 4
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpDecorate %_struct_6 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %int
+  %_struct_4 = OpTypeStruct %_struct_3
+%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
+  %_struct_6 = OpTypeStruct %_runtimearr__struct_4
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+       %void = OpTypeVoid
+         %13 = OpTypeFunction %void
+ %_struct_14 = OpTypeStruct %int
+ %_struct_15 = OpTypeStruct %_struct_14
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
+         %18 = OpTypeFunction %void %_ptr_Function__struct_15
+          %2 = OpVariable %_ptr_Uniform__struct_6 Uniform
+          %1 = OpFunction %void None %13
+         %19 = OpLabel
+         %20 = OpVariable %_ptr_Function__struct_15 Function
+         %21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
+         %22 = OpFunctionCall %void %23 %21
+               OpReturn
+               OpFunctionEnd
+         %23 = OpFunction %void None %18
+         %24 = OpFunctionParameter %_ptr_Function__struct_15
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMatchingPointersNestedArray) {
+  std::string spirv =
+      R"(
+              OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpDecorate %_arr_int_uint_10 ArrayStride 4
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpDecorate %_runtimearr__struct_4 ArrayStride 40
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpDecorate %_struct_6 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+    %uint_10 = OpConstant %uint 10
+%_arr_int_uint_10 = OpTypeArray %int %uint_10
+  %_struct_4 = OpTypeStruct %_arr_int_uint_10
+%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
+  %_struct_6 = OpTypeStruct %_runtimearr__struct_4
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
+%_arr_int_uint_10_0 = OpTypeArray %int %uint_10
+ %_struct_17 = OpTypeStruct %_arr_int_uint_10_0
+%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
+         %19 = OpTypeFunction %void %_ptr_Function__struct_17
+          %2 = OpVariable %_ptr_Uniform__struct_6 Uniform
+          %1 = OpFunction %void None %14
+         %20 = OpLabel
+         %21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
+         %22 = OpFunctionCall %void %23 %21
+               OpReturn
+               OpFunctionEnd
+         %23 = OpFunction %void None %19
+         %24 = OpFunctionParameter %_ptr_Function__struct_17
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersMissingMember) {
+  //  Validation should fail because the formal parameter type has two members,
+  //  while the actual parameter only has 1.
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+ %_struct_15 = OpTypeStruct %int %int
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+         %18 = OpTypeFunction %void %_ptr_Function__struct_15
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %14
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
+         %21 = OpFunctionCall %void %22 %20
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %void None %18
+         %23 = OpFunctionParameter %_ptr_Function__struct_15
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("type does not match Function <id>"));
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersDifferentMemberType) {
+  //  Validation should fail because the formal parameter has a member that is
+  // a different type than the actual parameter.
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %uint
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+ %_struct_15 = OpTypeStruct %int
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+         %18 = OpTypeFunction %void %_ptr_Function__struct_15
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %14
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
+         %21 = OpFunctionCall %void %22 %20
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %void None %18
+         %23 = OpFunctionParameter %_ptr_Function__struct_15
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("type does not match Function <id>"));
+}
+
+TEST_F(ValidateFunctionCall,
+       LogicallyMismatchedPointersIncompatableDecorations) {
+  //  Validation should fail because the formal parameter has an incompatible
+  //  decoration.
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 Block
+               OpMemberDecorate %_struct_15 0 NonWritable
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_StorageBuffer__struct_5 = OpTypePointer StorageBuffer %_struct_5
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+ %_struct_15 = OpTypeStruct %int
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+         %18 = OpTypeFunction %void %_ptr_Function__struct_15
+          %2 = OpVariable %_ptr_StorageBuffer__struct_5 StorageBuffer
+          %1 = OpFunction %void None %14
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_StorageBuffer__struct_3 %2 %int_0 %uint_0
+         %21 = OpFunctionCall %void %22 %20
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %void None %18
+         %23 = OpFunctionParameter %_ptr_Function__struct_15
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("type does not match Function <id>"));
+}
+
+TEST_F(ValidateFunctionCall,
+       LogicallyMismatchedPointersIncompatableDecorations2) {
+  //  Validation should fail because the formal parameter has an incompatible
+  //  decoration.
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+               OpDecorate %_ptr_Uniform__struct_3 ArrayStride 4
+               OpDecorate %_ptr_Uniform__struct_3_0 ArrayStride 8
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %_struct_3 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+%_ptr_Uniform__struct_3_0 = OpTypePointer Uniform %_struct_3
+         %18 = OpTypeFunction %void %_ptr_Uniform__struct_3_0
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %14
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
+         %21 = OpFunctionCall %void %22 %20
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %void None %18
+         %23 = OpFunctionParameter %_ptr_Uniform__struct_3_0
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("type does not match Function <id>"));
+}
+
+TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersArraySize) {
+  //  Validation should fail because the formal parameter array has a different
+  // number of element than the actual parameter.
+  std::string spirv =
+      R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpDecorate %_arr_int_uint_10 ArrayStride 4
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpDecorate %_runtimearr__struct_4 ArrayStride 40
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpDecorate %_struct_6 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+    %uint_5 = OpConstant %uint 5
+    %uint_10 = OpConstant %uint 10
+%_arr_int_uint_10 = OpTypeArray %int %uint_10
+  %_struct_4 = OpTypeStruct %_arr_int_uint_10
+%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
+  %_struct_6 = OpTypeStruct %_runtimearr__struct_4
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
+%_arr_int_uint_5 = OpTypeArray %int %uint_5
+ %_struct_17 = OpTypeStruct %_arr_int_uint_5
+%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
+         %19 = OpTypeFunction %void %_ptr_Function__struct_17
+          %2 = OpVariable %_ptr_Uniform__struct_6 Uniform
+          %1 = OpFunction %void None %14
+         %20 = OpLabel
+         %21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
+         %22 = OpFunctionCall %void %23 %21
+               OpReturn
+               OpFunctionEnd
+         %23 = OpFunction %void None %19
+         %24 = OpFunctionParameter %_ptr_Function__struct_17
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("type does not match Function <id>"));
+}
+
+INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
+                         Values("UniformConstant", "Input", "Uniform", "Output",
+                                "Workgroup", "Private", "Function",
+                                "PushConstant", "Image", "StorageBuffer",
+                                "AtomicCounter"));
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index a09761e..299e38e 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -314,7 +314,7 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Result id of OpDecorationGroup can only "
                         "be targeted by OpName, OpGroupDecorate, "
-                        "OpDecorate, and OpGroupMemberDecorate"));
+                        "OpDecorate, OpDecorateId, and OpGroupMemberDecorate"));
 }
 TEST_F(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) {
   std::string spirv = R"(
@@ -857,8 +857,8 @@
 // capability prohibits usage of signed integers, we can skip 8-bit integers
 // here since the purpose of these tests is to check the validity of
 // OpTypeArray, not OpTypeInt.
-INSTANTIATE_TEST_CASE_P(Widths, OpTypeArrayLengthTest,
-                        ValuesIn(std::vector<int>{16, 32, 64}));
+INSTANTIATE_TEST_SUITE_P(Widths, OpTypeArrayLengthTest,
+                         ValuesIn(std::vector<int>{16, 32, 64}));
 
 TEST_F(ValidateIdWithMessage, OpTypeArrayLengthNull) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -937,6 +937,26 @@
                         "a type."));
 }
 
+TEST_F(ValidateIdWithMessage, OpTypeStructOpaqueTypeBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+          %1 = OpTypeSampler
+          %2 = OpTypeStruct %1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               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(),
+              HasSubstr("OpTypeStruct must not contain an opaque type"));
+}
+
 TEST_F(ValidateIdWithMessage, OpTypePointerGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
@@ -2178,6 +2198,47 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateIdWithMessage, OpFunctionWithNonMemoryObject) {
+  // DXC generates code that looks like when given something like:
+  //   T t;
+  //   t.s.fn_1();
+  // This needs to be accepted before legalization takes place, so we
+  // will include it with the relaxed logical pointer.
+
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+               OpSource HLSL 600
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+  %_struct_5 = OpTypeStruct
+  %_struct_6 = OpTypeStruct %_struct_5
+%_ptr_Function__struct_6 = OpTypePointer Function %_struct_6
+%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+         %23 = OpTypeFunction %void %_ptr_Function__struct_5
+          %1 = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpVariable %_ptr_Function__struct_6 Function
+         %20 = OpAccessChain %_ptr_Function__struct_5 %11 %int_0
+         %21 = OpFunctionCall %void %12 %20
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %void None %23
+         %13 = OpFunctionParameter %_ptr_Function__struct_5
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto options = getValidatorOptions();
+  options->relax_logical_pointer = true;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateIdWithMessage,
        OpVariablePointerVariablePointersStorageBufferGood) {
   const std::string spirv = R"(
@@ -2274,6 +2335,7 @@
     *spirv << "OpCapability VariablePointers ";
     *spirv << "OpExtension \"SPV_KHR_variable_pointers\" ";
   }
+  *spirv << "OpExtension \"SPV_KHR_storage_buffer_storage_class\" ";
   *spirv << R"(
     OpMemoryModel Logical GLSL450
     OpEntryPoint GLCompute %main "main"
@@ -2282,12 +2344,12 @@
     %bool      = OpTypeBool
     %i32       = OpTypeInt 32 1
     %f32       = OpTypeFloat 32
-    %f32ptr    = OpTypePointer Uniform %f32
+    %f32ptr    = OpTypePointer StorageBuffer %f32
     %i         = OpConstant %i32 1
     %zero      = OpConstant %i32 0
     %float_1   = OpConstant %f32 1.0
-    %ptr1      = OpVariable %f32ptr Uniform
-    %ptr2      = OpVariable %f32ptr Uniform
+    %ptr1      = OpVariable %f32ptr StorageBuffer
+    %ptr2      = OpVariable %f32ptr StorageBuffer
   )";
   if (add_helper_function) {
     *spirv << R"(
@@ -3918,7 +3980,7 @@
 }
 
 // Run tests for Access Chain Instructions.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CheckAccessChainInstructions, AccessChainInstructionTest,
     ::testing::Values("OpAccessChain", "OpInBoundsAccessChain",
                       "OpPtrAccessChain", "OpInBoundsPtrAccessChain"));
@@ -6161,20 +6223,22 @@
 %4 = OpTypeFunction %3
 %5 = OpFunction %1 None %2
 %6 = OpLabel
-%7 = OpFunctionCall %3 %8
+OpReturn
+%7 = OpLabel
+%8 = OpFunctionCall %3 %9
 OpUnreachable
 OpFunctionEnd
-%8 = OpFunction %3 None %4
-%9 = OpLabel
-OpReturnValue %7
+%9 = OpFunction %3 None %4
+%10 = OpLabel
+OpReturnValue %8
 OpFunctionEnd
 )";
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 7[%7] defined in block 6[%6] does not dominate its "
-                        "use in block 9[%9]\n  %9 = OpLabel"));
+              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+                        "use in block 10[%10]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 79aecb2..fd3d886 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -25,6 +25,7 @@
 namespace val {
 namespace {
 
+using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
@@ -34,6 +35,7 @@
     const std::string& body,
     const std::string& capabilities_and_extensions = "",
     const std::string& execution_model = "Fragment",
+    const std::string& execution_mode = "",
     const spv_target_env env = SPV_ENV_UNIVERSAL_1_0,
     const std::string& memory_model = "GLSL450") {
   std::ostringstream ss;
@@ -54,12 +56,37 @@
     ss << "OpCapability SampledRect\n";
   }
 
+  // In 1.4, the entry point must list all module-scope variables used.  Just
+  // list all of them.
+  std::string interface_vars = (env != SPV_ENV_UNIVERSAL_1_4) ? "" :
+                                                              R"(
+%uniform_image_f32_1d_0001
+%uniform_image_f32_1d_0002_rgba32f
+%uniform_image_f32_2d_0001
+%uniform_image_f32_2d_0010
+%uniform_image_u32_2d_0001
+%uniform_image_u32_2d_0000
+%uniform_image_s32_3d_0001
+%uniform_image_f32_2d_0002
+%uniform_image_s32_2d_0002
+%uniform_image_f32_spd_0002
+%uniform_image_f32_3d_0111
+%uniform_image_f32_cube_0101
+%uniform_image_f32_cube_0102_rgba32f
+%uniform_sampler
+%private_image_u32_buffer_0002_r32ui
+%private_image_u32_spd_0002
+%private_image_f32_buffer_0002_r32ui
+)";
+
   ss << capabilities_and_extensions;
   ss << "OpMemoryModel Logical " << memory_model << "\n";
-  ss << "OpEntryPoint " << execution_model << " %main \"main\"\n";
+  ss << "OpEntryPoint " << execution_model
+     << " %main \"main\" " + interface_vars + "\n";
   if (execution_model == "Fragment") {
     ss << "OpExecutionMode %main OriginUpperLeft\n";
   }
+  ss << execution_mode;
 
   if (env == SPV_ENV_VULKAN_1_0) {
     ss << R"(
@@ -79,6 +106,8 @@
 OpDecorate %uniform_image_s32_3d_0001 Binding 2
 OpDecorate %uniform_image_f32_2d_0002 DescriptorSet 1
 OpDecorate %uniform_image_f32_2d_0002 Binding 3
+OpDecorate %uniform_image_s32_2d_0002 DescriptorSet 1
+OpDecorate %uniform_image_s32_2d_0002 Binding 4
 OpDecorate %uniform_image_f32_spd_0002 DescriptorSet 2
 OpDecorate %uniform_image_f32_spd_0002 Binding 0
 OpDecorate %uniform_image_f32_3d_0111 DescriptorSet 2
@@ -222,6 +251,11 @@
 %uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0002 UniformConstant
 %type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002
 
+%type_image_s32_2d_0002 = OpTypeImage %s32 2D 0 0 0 2 Unknown
+%ptr_image_s32_2d_0002 = OpTypePointer UniformConstant %type_image_s32_2d_0002
+%uniform_image_s32_2d_0002 = OpVariable %ptr_image_s32_2d_0002 UniformConstant
+%type_sampled_image_s32_2d_0002 = OpTypeSampledImage %type_image_s32_2d_0002
+
 %type_image_f32_spd_0002 = OpTypeImage %f32 SubpassData 0 0 0 2 Unknown
 %ptr_image_f32_spd_0002 = OpTypePointer UniformConstant %type_image_f32_spd_0002
 %uniform_image_f32_spd_0002 = OpVariable %ptr_image_f32_spd_0002 UniformConstant
@@ -574,7 +608,7 @@
 )";
 
   const spv_target_env env = SPV_ENV_VULKAN_1_0;
-  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env), env);
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "", env), env);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(env));
 }
 
@@ -626,7 +660,7 @@
 )";
 
   const spv_target_env env = SPV_ENV_VULKAN_1_0;
-  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env), env);
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "", env), env);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Expected Image 'Sampled' parameter to "
@@ -703,7 +737,7 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 140[%140] cannot be a "
                                                "type"));
 }
 
@@ -815,7 +849,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -935,7 +969,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1520,7 +1554,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1654,7 +1688,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1775,7 +1809,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1898,7 +1932,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2022,7 +2056,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2145,7 +2179,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2248,7 +2282,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2283,7 +2317,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
+%res1 = OpImageFetch %f32vec4 %sampler %u32vec2_01
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
@@ -2292,6 +2326,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, FetchSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageFetch"));
+}
+
 TEST_F(ValidateImage, FetchNotSampled) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
@@ -2392,7 +2441,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2648,7 +2697,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -2733,7 +2782,19 @@
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ReadNeedCapabilityStorageImageReadWithoutFormatVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01
+)";
+
+  spv_target_env env = SPV_ENV_VULKAN_1_0;
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "", env).c_str(),
+                      env);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Capability StorageImageReadWithoutFormat is required "
                         "to read storage image"));
@@ -2939,7 +3000,19 @@
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, WriteNeedCapabilityStorageImageWriteWithoutFormatVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123
+)";
+
+  spv_target_env env = SPV_ENV_VULKAN_1_0;
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "", env).c_str(),
+                      env);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -3191,7 +3264,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryFormat %u32 %simg
+%res1 = OpImageQueryFormat %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3227,7 +3300,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryOrder %u32 %simg
+%res1 = OpImageQueryOrder %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3276,7 +3349,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
+%res1 = OpImageQuerySizeLod %u32vec2 %sampler %u32_1
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3285,6 +3358,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QuerySizeLodSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQuerySizeLod"));
+}
+
 TEST_F(ValidateImage, QuerySizeLodWrongImageDim) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@@ -3348,7 +3436,7 @@
 %img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQuerySize %u32vec2 %simg
+%res1 = OpImageQuerySize %u32vec2 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3357,6 +3445,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QuerySizeSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQuerySize %u32vec2 %simg
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQuerySize"));
+}
+
 TEST_F(ValidateImage, QuerySizeDimSubpassDataBad) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002
@@ -3531,7 +3634,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryLevels %u32 %simg
+%res1 = OpImageQueryLevels %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3540,6 +3643,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QueryLevelsSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQueryLevels %u32 %simg
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQueryLevels"));
+}
+
 TEST_F(ValidateImage, QueryLevelsWrongDim) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@@ -3594,8 +3712,10 @@
 
   CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpImageQueryLod requires Fragment execution model"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpImageQueryLod requires Fragment or GLCompute execution model"));
 }
 
 TEST_F(ValidateImage, QueryLodWrongExecutionModelWithFunc) {
@@ -3613,8 +3733,55 @@
 
   CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpImageQueryLod requires Fragment or GLCompute execution model"));
+}
+
+TEST_F(ValidateImage, QueryLodComputeShaderDerivatives) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh
+)";
+
+  const std::string extra = R"(
+OpCapability ComputeDerivativeGroupLinearNV
+OpExtension "SPV_NV_compute_shader_derivatives"
+)";
+  const std::string mode = R"(
+OpExecutionMode %main LocalSize 8 8 1
+OpExecutionMode %main DerivativeGroupLinearNV
+)";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "GLCompute", mode).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, QueryLodComputeShaderDerivativesMissingMode) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh
+)";
+
+  const std::string extra = R"(
+OpCapability ComputeDerivativeGroupLinearNV
+OpExtension "SPV_NV_compute_shader_derivatives"
+)";
+  const std::string mode = R"(
+OpExecutionMode %main LocalSize 8 8 1
+)";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "GLCompute", mode).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpImageQueryLod requires Fragment execution model"));
+              HasSubstr("OpImageQueryLod requires DerivativeGroupQuadsNV or "
+                        "DerivativeGroupLinearNV execution mode for GLCompute "
+                        "execution model"));
 }
 
 TEST_F(ValidateImage, ImplicitLodWrongExecutionModel) {
@@ -3627,9 +3794,55 @@
 
   CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImplicitLod instructions require Fragment or "
+                        "GLCompute execution model"));
+}
+
+TEST_F(ValidateImage, ImplicitLodComputeShaderDerivatives) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh
+)";
+
+  const std::string extra = R"(
+OpCapability ComputeDerivativeGroupLinearNV
+OpExtension "SPV_NV_compute_shader_derivatives"
+)";
+  const std::string mode = R"(
+OpExecutionMode %main LocalSize 8 8 1
+OpExecutionMode %main DerivativeGroupLinearNV
+)";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "GLCompute", mode).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ImplicitLodComputeShaderDerivativesMissingMode) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh
+)";
+
+  const std::string extra = R"(
+OpCapability ComputeDerivativeGroupLinearNV
+OpExtension "SPV_NV_compute_shader_derivatives"
+)";
+  const std::string mode = R"(
+OpExecutionMode %main LocalSize 8 8 1
+)";
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "GLCompute", mode).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("ImplicitLod instructions require Fragment execution model"));
+      HasSubstr("ImplicitLod instructions require DerivativeGroupQuadsNV or "
+                "DerivativeGroupLinearNV execution mode for GLCompute "
+                "execution model"));
 }
 
 TEST_F(ValidateImage, ReadSubpassDataWrongExecutionModel) {
@@ -3663,7 +3876,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -3791,7 +4004,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -3885,7 +4098,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4102,7 +4315,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4243,7 +4456,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4260,7 +4473,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4279,7 +4492,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4301,7 +4514,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4322,7 +4535,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4341,7 +4554,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4362,7 +4575,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4383,7 +4596,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4406,7 +4619,7 @@
 OpCapability VulkanMemoryModelDeviceScopeKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -4423,7 +4636,7 @@
 OpCapability VulkanMemoryModelKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA,
@@ -4446,12 +4659,191 @@
 OpCapability VulkanMemoryModelDeviceScopeKHR
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
-  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment",
+  CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment", "",
                                          SPV_ENV_UNIVERSAL_1_3, "VulkanKHR")
                           .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+// This example used to cause a seg fault on OpReturnValue, verifying it doesn't
+// anymore.
+TEST_F(ValidateImage, Issue2463NoSegFault) {
+  const std::string spirv = R"(
+               OpCapability Linkage
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %8 = OpTypeImage %float 3D 0 0 0 1 Unknown
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+         %10 = OpTypeSampler
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+         %12 = OpTypeSampledImage %8
+         %13 = OpTypeFunction %12 %_ptr_UniformConstant_8 %_ptr_UniformConstant_10
+         %23 = OpFunction %12 None %13
+         %24 = OpFunctionParameter %_ptr_UniformConstant_8
+         %25 = OpFunctionParameter %_ptr_UniformConstant_10
+         %26 = OpLabel
+         %27 = OpLoad %8 %24
+         %28 = OpLoad %10 %25
+         %29 = OpSampledImage %12 %27 %28
+               OpReturnValue %29
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpReturnValue"));
+}
+
+TEST_F(ValidateImage, SignExtendV13Bad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
+)";
+
+  EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment", "",
+                                                SPV_ENV_UNIVERSAL_1_3)),
+              HasSubstr("Invalid image operand 'SignExtend'"));
+}
+
+TEST_F(ValidateImage, ZeroExtendV13Bad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
+)";
+
+  EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment", "",
+                                                SPV_ENV_UNIVERSAL_1_3)),
+              HasSubstr("Invalid image operand 'ZeroExtend'"));
+}
+
+TEST_F(ValidateImage, SignExtendScalarUIntTexelV14Good) {
+  // Unsigned int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendScalarSIntTexelV14Good) {
+  // Signed int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendScalarVectorUIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendVectorSIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32vec4 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// No negative tests for SignExtend since we don't truly know the
+// texel format.
+
+TEST_F(ValidateImage, ZeroExtendScalarUIntTexelV14Good) {
+  // Unsigned int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendScalarSIntTexelV14Good) {
+  // Zeroed int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendScalarVectorUIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendVectorSIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32vec4 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", "", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// No negative tests for ZeroExtend since we don't truly know the
+// texel format.
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index ce430f6..3410616 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -48,8 +48,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, EntryPointMissingOutput) {
@@ -74,8 +75,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Output variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, InterfaceMissingUseInSubfunction) {
@@ -105,8 +107,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, TwoEntryPointsOneFunction) {
@@ -132,8 +135,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <2> is used by entry point 'func2' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <2> is used by entry point 'func2' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, MissingInterfaceThroughInitializer) {
@@ -160,8 +164,239 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <6> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <6> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
+}
+
+TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var %var
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_struct Input
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_struct Input
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Non-unique OpEntryPoint interface 2[%var] is disallowed"));
+}
+
+TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Interface variable id <2> is used by entry point "
+                        "'main' id <1>, but is not listed as an interface"));
+}
+
+TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Function %struct
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%var = OpVariable %ptr_struct Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpEntryPoint interfaces must be OpVariables with "
+                        "Storage Class of Input(1) or Output(3). Found Storage "
+                        "Class 7 for Entry Point id 1."));
+}
+
+TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Function %struct
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%var = OpVariable %ptr_struct Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpEntryPoint interfaces should only list global variables"));
+}
+
+TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_NotAllUsedGlobals) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_DuplicateInterface) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %gid %gid
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %gid BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateInterfacesTest, SPV14MultipleEntryPointsSameFunction) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main1" %gid
+OpEntryPoint GLCompute %main "main2" %gid
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %gid BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
 }
 
 }  // namespace
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index e502d8c..9d4491c 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -120,7 +120,7 @@
 static const int kRangeEnd = 1000;
 pred_type All = Range<0, kRangeEnd>();
 
-INSTANTIATE_TEST_CASE_P(InstructionsOrder,
+INSTANTIATE_TEST_SUITE_P(InstructionsOrder,
     ValidateLayout,
     ::testing::Combine(::testing::Range((int)0, (int)getInstructions().size()),
     // Note: Because of ID dependencies between instructions, some instructions
@@ -160,7 +160,7 @@
                     , std::make_tuple(std::string("%fLabel   = OpLabel")       , Equals<39>             , All)
                     , std::make_tuple(std::string("OpNop")                     , Equals<40>             , Range<40,kRangeEnd>())
                     , std::make_tuple(std::string("OpReturn ; %func2 return")  , Equals<41>             , All)
-    )),);
+    )));
 // clang-format on
 
 // Creates a new vector which removes the string if the substr is found in the
@@ -181,7 +181,7 @@
 }
 
 // This test will check the logical layout of a binary by removing each
-// instruction in the pair of the INSTANTIATE_TEST_CASE_P call and moving it in
+// instruction in the pair of the INSTANTIATE_TEST_SUITE_P call and moving it in
 // the SPIRV source formed by combining the vector "instructions".
 TEST_P(ValidateLayout, Layout) {
   int order;
diff --git a/test/val/val_limits_test.cpp b/test/val/val_limits_test.cpp
index 791b709..becf7be 100644
--- a/test/val/val_limits_test.cpp
+++ b/test/val/val_limits_test.cpp
@@ -754,13 +754,17 @@
            OpName %loop "loop"
            OpName %exit "exit"
 %voidt   = OpTypeVoid
+%boolt   = OpTypeBool
+%undef   = OpUndef %boolt
 %funct   = OpTypeFunction %voidt
 %main    = OpFunction %voidt None %funct
 %entry   = OpLabel
            OpBranch %exit
 %loop    = OpLabel
-           OpLoopMerge %loop %loop None
-           OpBranch %loop
+           OpLoopMerge %dead %loop None
+           OpBranchConditional %undef %loop %loop
+%dead    = OpLabel
+           OpUnreachable
 %exit    = OpLabel
            OpReturn
            OpFunctionEnd
diff --git a/test/val/val_literals_test.cpp b/test/val/val_literals_test.cpp
index cbdbdd1..6eadf32 100644
--- a/test/val/val_literals_test.cpp
+++ b/test/val/val_literals_test.cpp
@@ -85,6 +85,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateLiterals, InvalidInt) {
+  std::string str = GenerateShaderCode() + R"(
+%11 = OpTypeInt 32 90
+  )";
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpTypeInt has invalid signedness:"));
+}
+
 TEST_P(ValidateLiteralsShader, LiteralsShaderBad) {
   std::string str = GenerateShaderCode() + GetParam();
   std::string inst_id = "11";
@@ -99,7 +109,7 @@
                 "or sign extended when Signedness is 1"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LiteralsShaderCases, ValidateLiteralsShader,
     ::testing::Values("%11 = OpConstant %int16  !0xFFFF0000",  // Sign bit is 0
                       "%11 = OpConstant %int16  !0x00008000",  // Sign bit is 1
@@ -132,7 +142,7 @@
                 "or sign extended when Signedness is 1"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LiteralsKernelCases, ValidateLiteralsKernel,
     ::testing::Values("%2 = OpConstant %uint8  !0xABCDEF00",
                       "%2 = OpConstant %uint8  !0xABCDEFFF"));
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp
index da8e7d9..b57c743 100644
--- a/test/val/val_logicals_test.cpp
+++ b/test/val/val_logicals_test.cpp
@@ -24,6 +24,7 @@
 namespace val {
 namespace {
 
+using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
@@ -144,6 +145,18 @@
 %boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true
 %boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false
 
+%arr_u32_2 = OpTypeArray %u32 %u32_2
+%st_u32_u32 = OpTypeStruct %u32 %u32
+%mat_f32_2_2 = OpTypeMatrix %f32vec2 2
+
+%nul_arr_u32_2 = OpConstantNull %arr_u32_2
+%nul_st_u32_u32 = OpConstantNull %st_u32_u32
+%nul_mat_f32_2_2 = OpConstantNull %mat_f32_2_2
+
+%arr_u32_2_1_2 = OpConstantComposite %arr_u32_2 %u32_1 %u32_2
+%st_u32_u32_1_2 = OpConstantComposite %st_u32_u32 %u32_1 %u32_2
+%mat_f32_2_2_01_12 = OpConstantComposite %mat_f32_2_2 %f32vec2_01 %f32vec2_12
+
 %f32vec4ptr = OpTypePointer Function %f32vec4
 
 %main = OpFunction %void None %func
@@ -585,6 +598,20 @@
       HasSubstr("Expected scalar or vector type as Result Type: Select"));
 }
 
+TEST_F(ValidateLogicals, OpSelectWrongTypeIdV14) {
+  // In 1.4, the message changes to allow composites.
+  const std::string body = R"(
+%val1 = OpSelect %void %true %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or composite type as Result Type: Select"));
+}
+
 TEST_F(ValidateLogicals, OpSelectPointerNoCapability) {
   const std::string body = R"(
 %x = OpVariable %f32vec4ptr Function
@@ -687,6 +714,111 @@
               HasSubstr("Expected both objects to be of Result Type: Select"));
 }
 
+TEST_F(ValidateLogicals, OpSelectArrayV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectArrayV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectArrayV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateLogicals, OpIEqualSuccess) {
   const std::string body = R"(
 %val1 = OpIEqual %bool %u32_0 %s32_1
@@ -949,6 +1081,84 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateLogicals, SelectVectorsScalarCondition) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %true %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected vector sizes of Result Type and the "
+                        "condition to be equal: Select"));
+}
+
+TEST_F(ValidateLogicals, SelectVectorsScalarCondition1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %true %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateLogicals, SelectVectorsVectorConditionMismatchedDimensions1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool3 = OpTypeVector %bool 3
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%bool3_null = OpConstantNull %bool3
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %bool3_null %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected vector sizes of Result Type and the "
+                        "condition to be equal: Select"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 097477c..54009df 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -19,14 +19,17 @@
 
 #include "gmock/gmock.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+using ::testing::Combine;
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::Values;
 
 using ValidateMemory = spvtest::ValidateBase<bool>;
 
@@ -449,12 +452,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
-          "class combination.\nFrom WebGPU execution environment spec:\n"
-          "Variable declarations that include initializers must have one of "
-          "the following storage classes: Output, Private, or Function\n"
-          "  %5 = OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
+      HasSubstr("OpVariable, <id> '5[%5]', has a disallowed initializer & "
+                "storage class combination.\nFrom WebGPU spec:\nVariable "
+                "declarations that include initializers must have one of the "
+                "following storage classes: Output, Private, or Function\n  %5 "
+                "= OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
 }
 
 TEST_F(ValidateMemory, WebGPUOutputStorageClassWithoutInitializerBad) {
@@ -628,12 +630,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
-          "class combination.\nFrom Vulkan spec, Appendix A:\n"
-          "Variable declarations that include initializers must have one of "
-          "the following storage classes: Output, Private, or Function\n  "
-          "%5 = OpVariable %_ptr_Input_float Input %float_1\n"));
+      HasSubstr("OpVariable, <id> '5[%5]', has a disallowed initializer & "
+                "storage class combination.\nFrom Vulkan spec:\nVariable "
+                "declarations that include initializers must have one of the "
+                "following storage classes: Output, Private, or Function\n  %5 "
+                "= OpVariable %_ptr_Input_float Input %float_1\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenCorrectResultType) {
@@ -1344,32 +1345,6 @@
                 "VulkanMemoryModelDeviceScopeKHR capability"));
 }
 
-TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood1) {
-  const std::string spirv = R"(
-OpCapability Shader
-OpCapability VulkanMemoryModelKHR
-OpCapability VulkanMemoryModelDeviceScopeKHR
-OpCapability Linkage
-OpExtension "SPV_KHR_vulkan_memory_model"
-OpMemoryModel Logical VulkanKHR
-%void = OpTypeVoid
-%int = OpTypeInt 32 0
-%device = OpConstant %int 1
-%int_ptr_ssbo = OpTypePointer StorageBuffer %int
-%var1 = OpVariable %int_ptr_ssbo StorageBuffer
-%var2 = OpVariable %int_ptr_ssbo StorageBuffer
-%voidfn = OpTypeFunction %void
-%func = OpFunction %void None %voidfn
-%entry = OpLabel
-OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
-OpReturn
-OpFunctionEnd
-)";
-
-  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-}
-
 TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1424,6 +1399,138 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisBadBinaryV13) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "with two memory access operands requires SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessFirstWithAvBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Source memory access must not include MakePointerAvailableKHR\n"
+          "  OpCopyMemory %5 %6 MakePointerAvailableKHR|NonPrivatePointerKHR"
+          " %uint_1 MakePointerAvailableKHR|NonPrivatePointerKHR %uint_1"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessSecondWithVisBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Target memory access must not include MakePointerVisibleKHR\n"
+          "  OpCopyMemory %5 %6 MakePointerVisibleKHR|NonPrivatePointerKHR"
+          " %uint_1 MakePointerVisibleKHR|NonPrivatePointerKHR %uint_1"));
+}
+
 TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1769,11 +1876,377 @@
 OpFunctionEnd
 )";
 
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PhysicalStorageBufferEXT must not be used with OpVariable"));
+}
+
+std::string GenCoopMatLoadStoreShader(const std::string& storeMemoryAccess,
+                                      const std::string& loadMemoryAccess) {
+  std::string s = R"(
+OpCapability Shader
+OpCapability GroupNonUniform
+OpCapability VulkanMemoryModelKHR
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_NV_cooperative_matrix"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint GLCompute %4 "main" %11 %21
+OpExecutionMode %4 LocalSize 1 1 1
+OpDecorate %11 BuiltIn SubgroupId
+OpDecorate %21 BuiltIn WorkgroupId
+OpDecorate %74 ArrayStride 4
+OpMemberDecorate %75 0 Offset 0
+OpDecorate %75 Block
+OpDecorate %77 DescriptorSet 0
+OpDecorate %77 Binding 0
+OpDecorate %92 ArrayStride 4
+OpMemberDecorate %93 0 Offset 0
+OpDecorate %93 Block
+OpDecorate %95 DescriptorSet 0
+OpDecorate %95 Binding 1
+OpDecorate %102 ArrayStride 4
+OpMemberDecorate %103 0 Offset 0
+OpDecorate %103 Block
+OpDecorate %105 DescriptorSet 0
+OpDecorate %105 Binding 2
+OpDecorate %117 ArrayStride 4
+OpMemberDecorate %118 0 Offset 0
+OpDecorate %118 Block
+OpDecorate %120 DescriptorSet 0
+OpDecorate %120 Binding 3
+OpDecorate %123 SpecId 2
+OpDecorate %124 SpecId 3
+OpDecorate %125 SpecId 4
+OpDecorate %126 SpecId 5
+OpDecorate %127 SpecId 0
+OpDecorate %128 SpecId 1
+OpDecorate %129 BuiltIn WorkgroupSize
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpTypeVector %6 2
+%8 = OpTypePointer Function %7
+%10 = OpTypePointer Input %6
+%11 = OpVariable %10 Input
+%13 = OpConstant %6 2
+%19 = OpTypeVector %6 3
+%20 = OpTypePointer Input %19
+%21 = OpVariable %20 Input
+%27 = OpConstantComposite %7 %13 %13
+%31 = OpTypePointer Function %6
+%33 = OpConstant %6 1024
+%34 = OpConstant %6 1
+%38 = OpConstant %6 8
+%39 = OpConstant %6 0
+%68 = OpTypeFloat 32
+%69 = OpConstant %6 16
+%70 = OpConstant %6 3
+%71 = OpTypeCooperativeMatrixNV %68 %70 %69 %38
+%72 = OpTypePointer Function %71
+%74 = OpTypeRuntimeArray %68
+%75 = OpTypeStruct %74
+%76 = OpTypePointer StorageBuffer %75
+%77 = OpVariable %76 StorageBuffer
+%78 = OpTypeInt 32 1
+%79 = OpConstant %78 0
+%81 = OpConstant %6 5
+%82 = OpTypePointer StorageBuffer %68
+%84 = OpConstant %6 64
+%85 = OpTypeBool
+%86 = OpConstantFalse %85
+%88 = OpTypePointer Private %71
+%89 = OpVariable %88 Private
+%92 = OpTypeRuntimeArray %68
+%93 = OpTypeStruct %92
+%94 = OpTypePointer StorageBuffer %93
+%95 = OpVariable %94 StorageBuffer
+%99 = OpVariable %88 Private
+%102 = OpTypeRuntimeArray %68
+%103 = OpTypeStruct %102
+%104 = OpTypePointer StorageBuffer %103
+%105 = OpVariable %104 StorageBuffer
+%109 = OpVariable %88 Private
+%111 = OpVariable %88 Private
+%112 = OpSpecConstantOp %6 CooperativeMatrixLengthNV %71
+%113 = OpSpecConstantOp %78 IAdd %112 %79
+%117 = OpTypeRuntimeArray %68
+%118 = OpTypeStruct %117
+%119 = OpTypePointer StorageBuffer %118
+%120 = OpVariable %119 StorageBuffer
+%123 = OpSpecConstant %78 1
+%124 = OpSpecConstant %78 1
+%125 = OpSpecConstant %78 1
+%126 = OpSpecConstant %78 1
+%127 = OpSpecConstant %6 1
+%128 = OpSpecConstant %6 1
+%129 = OpSpecConstantComposite %19 %127 %128 %34
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%9 = OpVariable %8 Function
+%18 = OpVariable %8 Function
+%32 = OpVariable %31 Function
+%44 = OpVariable %31 Function
+%52 = OpVariable %31 Function
+%60 = OpVariable %31 Function
+%73 = OpVariable %72 Function
+%91 = OpVariable %72 Function
+%101 = OpVariable %72 Function
+%12 = OpLoad %6 %11
+%14 = OpUMod %6 %12 %13
+%15 = OpLoad %6 %11
+%16 = OpUDiv %6 %15 %13
+%17 = OpCompositeConstruct %7 %14 %16
+OpStore %9 %17
+%22 = OpLoad %19 %21
+%23 = OpVectorShuffle %7 %22 %22 0 1
+%24 = OpCompositeExtract %6 %23 0
+%25 = OpCompositeExtract %6 %23 1
+%26 = OpCompositeConstruct %7 %24 %25
+%28 = OpIMul %7 %26 %27
+%29 = OpLoad %7 %9
+%30 = OpIAdd %7 %28 %29
+OpStore %18 %30
+%35 = OpAccessChain %31 %18 %34
+%36 = OpLoad %6 %35
+%37 = OpIMul %6 %33 %36
+%40 = OpAccessChain %31 %18 %39
+%41 = OpLoad %6 %40
+%42 = OpIMul %6 %38 %41
+%43 = OpIAdd %6 %37 %42
+OpStore %32 %43
+%45 = OpAccessChain %31 %18 %34
+%46 = OpLoad %6 %45
+%47 = OpIMul %6 %33 %46
+%48 = OpAccessChain %31 %18 %39
+%49 = OpLoad %6 %48
+%50 = OpIMul %6 %38 %49
+%51 = OpIAdd %6 %47 %50
+OpStore %44 %51
+%53 = OpAccessChain %31 %18 %34
+%54 = OpLoad %6 %53
+%55 = OpIMul %6 %33 %54
+%56 = OpAccessChain %31 %18 %39
+%57 = OpLoad %6 %56
+%58 = OpIMul %6 %38 %57
+%59 = OpIAdd %6 %55 %58
+OpStore %52 %59
+%61 = OpAccessChain %31 %18 %34
+%62 = OpLoad %6 %61
+%63 = OpIMul %6 %33 %62
+%64 = OpAccessChain %31 %18 %39
+%65 = OpLoad %6 %64
+%66 = OpIMul %6 %38 %65
+%67 = OpIAdd %6 %63 %66
+OpStore %60 %67
+%80 = OpLoad %6 %32
+%83 = OpAccessChain %82 %77 %79 %80
+%87 = OpCooperativeMatrixLoadNV %71 %83 %84 %86 )" +
+                  loadMemoryAccess + R"( %81
+OpStore %73 %87
+%90 = OpLoad %71 %73
+OpStore %89 %90
+%96 = OpLoad %6 %44
+%97 = OpAccessChain %82 %95 %79 %96
+%98 = OpCooperativeMatrixLoadNV %71 %97 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
+OpStore %91 %98
+%100 = OpLoad %71 %91
+OpStore %99 %100
+%106 = OpLoad %6 %52
+%107 = OpAccessChain %82 %105 %79 %106
+%108 = OpCooperativeMatrixLoadNV %71 %107 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
+OpStore %101 %108
+%110 = OpLoad %71 %101
+OpStore %109 %110
+%114 = OpConvertSToF %68 %113
+%115 = OpCompositeConstruct %71 %114
+OpStore %111 %115
+%116 = OpLoad %71 %111
+%121 = OpLoad %6 %60
+%122 = OpAccessChain %82 %120 %79 %121
+OpCooperativeMatrixStoreNV %122 %116 %84 %86 )" + storeMemoryAccess + R"( %81
+OpReturn
+OpFunctionEnd
+)";
+
+  return s;
+}
+
+TEST_F(ValidateMemory, CoopMatLoadStoreSuccess) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
+                                "MakePointerVisibleKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, CoopMatStoreMemoryAccessFail) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerVisibleKHR|NonPrivatePointerKHR",
+                                "MakePointerVisibleKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore"));
+}
+
+TEST_F(ValidateMemory, CoopMatLoadMemoryAccessFail) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
+                                "MakePointerAvailableKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
+}
+
+TEST_F(ValidateMemory, CoopMatInvalidStorageClassFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%str = OpTypeStruct %f16mat
+%str_ptr = OpTypePointer Workgroup %str
+%sh = OpVariable %str_ptr Workgroup
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+OpReturn
+OpFunctionEnd)";
+
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("PhysicalStorageBufferEXT must not be used with OpVariable"));
+      HasSubstr(
+          "Cooperative matrix types (or types containing them) can only be "
+          "allocated in Function or Private storage classes or as function "
+          "parameters"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthResultTypeBad) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %i32 %f16mat
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The Result Type of OpCooperativeMatrixLengthNV <id> "
+                "'11[%11]' must be OpTypeInt with width 32 and signedness 0"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthOperandTypeBad) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %u32 %u32
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The type in OpCooperativeMatrixLengthNV <id> '5[%uint]' "
+                "must be OpTypeCooperativeMatrixNV"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthGood) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %u32 %f16mat
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
 TEST_F(ValidateMemory, VulkanRTAOutsideOfStructBad) {
@@ -2103,7 +2576,7 @@
 OpExecutionMode %func OriginUpperLeft
 OpDecorate %array_t ArrayStride 4
 OpMemberDecorate %struct_t 0 Offset 0
-OpDecorate %struct_t BufferBlock
+OpDecorate %struct_t Block
 %uint_t = OpTypeInt 32 0
 %array_t = OpTypeRuntimeArray %uint_t
 %struct_t = OpTypeStruct %array_t
@@ -2179,7 +2652,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
-          "valid in Vulkan environment.\n  %_runtimearr__runtimearr_2 = "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_2 = "
           "OpTypeRuntimeArray %_runtimearr_2\n"));
 }
 
@@ -2210,7 +2683,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
-          "valid in WebGPU environment.\n  %_runtimearr__runtimearr_2 = "
+          "valid in WebGPU environments.\n  %_runtimearr__runtimearr_2 = "
           "OpTypeRuntimeArray %_runtimearr_2\n"));
 }
 
@@ -2242,7 +2715,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '4[%_runtimearr_uint]' is not "
-          "valid in Vulkan environment.\n  %_runtimearr__runtimearr_uint = "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
           "OpTypeRuntimeArray %_runtimearr_uint\n"));
 }
 
@@ -2304,7 +2777,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
-          "valid in Vulkan environment.\n  %_runtimearr__runtimearr_uint = "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
           "OpTypeRuntimeArray %_runtimearr_uint\n"));
 }
 
@@ -2340,7 +2813,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
-          "valid in Vulkan environment.\n  %_runtimearr__runtimearr_uint = "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
           "OpTypeRuntimeArray %_runtimearr_uint\n"));
 }
 
@@ -2370,7 +2843,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
-                "valid in Vulkan environment.\n  %_arr__runtimearr_4_uint_1 = "
+                "valid in Vulkan environments.\n  %_arr__runtimearr_4_uint_1 = "
                 "OpTypeArray %_runtimearr_4 %uint_1\n"));
 }
 
@@ -2402,7 +2875,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
-                "valid in WebGPU environment.\n  %_arr__runtimearr_4_uint_1 = "
+                "valid in WebGPU environments.\n  %_arr__runtimearr_4_uint_1 = "
                 "OpTypeArray %_runtimearr_4 %uint_1\n"));
 }
 
@@ -2436,7 +2909,7 @@
       getDiagnosticString(),
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '6[%_runtimearr_uint]' is not "
-          "valid in Vulkan environment.\n  %_runtimearr__runtimearr_uint = "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
           "OpTypeRuntimeArray %_runtimearr_uint\n"));
 }
 
@@ -2468,9 +2941,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
-                "valid in Vulkan environment.\n  %_arr__runtimearr_uint_uint_1 "
-                "= OpTypeArray %_runtimearr_uint %uint_1\n"));
+      HasSubstr(
+          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
+          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
 }
 
 TEST_F(ValidateMemory,
@@ -2504,9 +2978,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
-                "valid in Vulkan environment.\n  %_arr__runtimearr_uint_uint_1 "
-                "= OpTypeArray %_runtimearr_uint %uint_1\n"));
+      HasSubstr(
+          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
+          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
 }
 
 TEST_F(ValidateMemory, VulkanRTAStructInsideRTAWithRuntimeDescriptorArrayGood) {
@@ -2568,6 +3043,1256 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
 
+TEST_F(ValidateMemory, CopyMemoryNoAccessGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleMixedAccessGood) {
+  // Test one memory access operand using features that don't require the
+  // Vulkan memory model.
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile|Aligned|Nontemporal 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV13Bad) {
+  // Two memory access operands is invalid up to SPIR-V 1.3
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("CopyMemory with two memory access operands requires "
+                        "SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV14Good) {
+  // Two memory access operands is valid in SPIR-V 1.4
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedNoAccessGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleMixedAccessGood) {
+  // Test one memory access operand using features that don't require the
+  // Vulkan memory model.
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile|Aligned|Nontemporal 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV13Bad) {
+  // Two memory access operands is invalid up to SPIR-V 1.3
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("CopyMemorySized with two memory access operands requires "
+                "SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV14Good) {
+  // Two memory access operands is valid in SPIR-V 1.4
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+using ValidatePointerComparisons = spvtest::ValidateBase<std::string>;
+
+TEST_P(ValidatePointerComparisons, Good) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_P(ValidatePointerComparisons, GoodWorkgroup) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Workgroup %int
+%var = OpVariable %ptr_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_P(ValidatePointerComparisons, BadResultType) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %bool ";
+  } else {
+    spirv += " %int ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  if (operation == "OpPtrDiff") {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Result Type must be an integer scalar"));
+  } else {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Result Type must be OpTypeBool"));
+  }
+}
+
+TEST_P(ValidatePointerComparisons, BadCapabilities) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  if (operation == "OpPtrDiff") {
+    // Gets caught by the grammar.
+    EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Instruction cannot be used without a variable "
+                          "pointers capability"));
+  }
+}
+
+TEST_P(ValidatePointerComparisons, BadOperandType) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%ld %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand type must be a pointer"));
+}
+
+TEST_P(ValidatePointerComparisons, BadStorageClassWorkgroup) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Workgroup %int
+%var = OpVariable %ptr_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Workgroup storage class pointer requires "
+                        "VariablePointers capability to be specified"));
+}
+
+TEST_P(ValidatePointerComparisons, BadStorageClass) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Private %int
+%var = OpVariable %ptr_int Private
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid pointer storage class"));
+}
+
+TEST_P(ValidatePointerComparisons, BadDiffOperandTypes) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Private %int
+%var = OpVariable %ptr_int Private
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The types of Operand 1 and Operand 2 must match"));
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons,
+                         Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff"));
+
+TEST_F(ValidateMemory, VariableInitializerWrongType) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%ptr_wg_int = OpTypePointer Workgroup %int
+%ptr_wg_float = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%ptr_private_wg_float = OpTypePointer Private %ptr_wg_float
+%priv_var = OpVariable %ptr_private_wg_float Private %wg_var
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Initializer type must match the type pointed to by "
+                        "the Result Type"));
+}
+
+TEST_F(ValidateMemory, StoreToUniformBlock) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_struct Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
+%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, StoreToUniformBlockVulkan) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_struct Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
+%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
+}
+
+// This test requires that the struct is not id 2.
+TEST_F(ValidateMemory, StoreToUniformBlockVulkan2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %gid_var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %3 Block
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %gid_var BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int3 = OpTypeVector %int 3
+%int4 = OpTypeVector %int 4
+%3 = OpTypeStruct %int4
+%ptr_uniform_struct = OpTypePointer Uniform %3
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_struct Uniform
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid_var = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
+%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
+}
+
+TEST_F(ValidateMemory, StoreToUniformBufferBlockVulkan) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct BufferBlock
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_struct Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
+%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, StoreToUniformBlockVulkanArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%array_struct = OpTypeArray %struct %int_1
+%ptr_uniform_array = OpTypePointer Uniform %array_struct
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_array Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int %var %int_0 %int_0 %int_0
+%gep2 = OpCopyObject %ptr_uniform_int %gep1
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
+}
+
+// This test requires that the struct is not id 2.
+TEST_F(ValidateMemory, StoreToUniformBlockVulkanArray2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %gid_var
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %gid_var BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int3 = OpTypeVector %int 3
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%array_struct = OpTypeArray %struct %int_1
+%ptr_uniform_array = OpTypePointer Uniform %array_struct
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_array Uniform
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid_var = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int %var %int_0 %int_0 %int_0
+%gep2 = OpCopyObject %ptr_uniform_int %gep1
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
+}
+
+TEST_F(ValidateMemory, StoreToUniformBlockVulkanRuntimeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int4 = OpTypeVector %int 4
+%struct = OpTypeStruct %int4
+%array_struct = OpTypeRuntimeArray %struct
+%ptr_uniform_array = OpTypePointer Uniform %array_struct
+%ptr_uniform_struct = OpTypePointer Uniform %struct
+%ptr_uniform_int4 = OpTypePointer Uniform %int4
+%ptr_uniform_int = OpTypePointer Uniform %int
+%var = OpVariable %ptr_uniform_array Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0 %int_0
+%gep2 = OpInBoundsAccessChain %ptr_uniform_int %gep1 %int_0
+OpStore %gep2 %int_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
+}
+
+using ValidateSizedVariable =
+    spvtest::ValidateBase<std::tuple<std::string, std::string, std::string>>;
+
+CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit) {
+  CodeGenerator generator;
+  generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n";
+  generator.extensions_ =
+      "OpExtension \"SPV_KHR_16bit_storage\"\nOpExtension "
+      "\"SPV_KHR_8bit_storage\"\n";
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  if (is_8bit) {
+    generator.before_types_ = R"(OpDecorate %char_buffer_block BufferBlock
+OpMemberDecorate %char_buffer_block 0 Offset 0
+)";
+    generator.types_ = R"(%void = OpTypeVoid
+%char = OpTypeInt 8 0
+%char4 = OpTypeVector %char 4
+%char_buffer_block = OpTypeStruct %char
+)";
+  } else {
+    generator.before_types_ = R"(OpDecorate %half_buffer_block BufferBlock
+OpDecorate %short_buffer_block BufferBlock
+OpMemberDecorate %half_buffer_block 0 Offset 0
+OpMemberDecorate %short_buffer_block 0 Offset 0
+)";
+    generator.types_ = R"(%void = OpTypeVoid
+%short = OpTypeInt 16 0
+%half = OpTypeFloat 16
+%short4 = OpTypeVector %short 4
+%half4 = OpTypeVector %half 4
+%mat4x4 = OpTypeMatrix %half4 4
+%short_buffer_block = OpTypeStruct %short
+%half_buffer_block = OpTypeStruct %half
+)";
+  }
+  generator.after_types_ = R"(%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+)";
+  generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n";
+  return generator;
+}
+
+TEST_P(ValidateSizedVariable, Capability) {
+  const std::string storage_class = std::get<0>(GetParam());
+  const std::string capability = std::get<1>(GetParam());
+  const std::string var_type = std::get<2>(GetParam());
+
+  bool type_8bit = false;
+  if (var_type == "%char" || var_type == "%char4" ||
+      var_type == "%char_buffer_block") {
+    type_8bit = true;
+  }
+
+  auto generator = GetSizedVariableCodeGenerator(type_8bit);
+  generator.types_ += "%ptr_type = OpTypePointer " + storage_class + " " +
+                      var_type + "\n%var = OpVariable %ptr_type " +
+                      storage_class + "\n";
+  generator.capabilities_ += "OpCapability " + capability + "\n";
+
+  bool capability_ok = false;
+  bool storage_class_ok = false;
+  if (storage_class == "Input" || storage_class == "Output") {
+    if (!type_8bit) {
+      capability_ok = capability == "StorageInputOutput16";
+      storage_class_ok = true;
+    }
+  } else if (storage_class == "StorageBuffer") {
+    if (type_8bit) {
+      capability_ok = capability == "StorageBuffer8BitAccess" ||
+                      capability == "UniformAndStorageBuffer8BitAccess";
+    } else {
+      capability_ok = capability == "StorageBuffer16BitAccess" ||
+                      capability == "UniformAndStorageBuffer16BitAccess";
+    }
+    storage_class_ok = true;
+  } else if (storage_class == "PushConstant") {
+    if (type_8bit) {
+      capability_ok = capability == "StoragePushConstant8";
+    } else {
+      capability_ok = capability == "StoragePushConstant16";
+    }
+    storage_class_ok = true;
+  } else if (storage_class == "Uniform") {
+    bool buffer_block = var_type.find("buffer_block") != std::string::npos;
+    if (type_8bit) {
+      capability_ok = capability == "UniformAndStorageBuffer8BitAccess" ||
+                      (capability == "StorageBuffer8BitAccess" && buffer_block);
+    } else {
+      capability_ok =
+          capability == "UniformAndStorageBuffer16BitAccess" ||
+          (capability == "StorageBuffer16BitAccess" && buffer_block);
+    }
+    storage_class_ok = true;
+  }
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  spv_result_t result = ValidateInstructions(SPV_ENV_UNIVERSAL_1_3);
+  if (capability_ok) {
+    EXPECT_EQ(SPV_SUCCESS, result);
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, result);
+    if (storage_class_ok) {
+      std::string message = std::string("Allocating a variable containing a ") +
+                            (type_8bit ? "8" : "16") + "-bit element in " +
+                            storage_class +
+                            " storage class requires an additional capability";
+      EXPECT_THAT(getDiagnosticString(), HasSubstr(message));
+    } else {
+      std::string message =
+          std::string("Cannot allocate a variable containing a ") +
+          (type_8bit ? "8" : "16") + "-bit type in " + storage_class +
+          " storage class";
+      EXPECT_THAT(getDiagnosticString(), HasSubstr(message));
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Storage8, ValidateSizedVariable,
+    Combine(Values("UniformConstant", "Input", "Output", "Workgroup",
+                   "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
+            Values("StorageBuffer8BitAccess",
+                   "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8"),
+            Values("%char", "%char4", "%char_buffer_block")));
+
+INSTANTIATE_TEST_SUITE_P(
+    Storage16, ValidateSizedVariable,
+    Combine(Values("UniformConstant", "Input", "Output", "Workgroup",
+                   "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
+            Values("StorageBuffer16BitAccess",
+                   "UniformAndStorageBuffer16BitAccess",
+                   "StoragePushConstant16", "StorageInputOutput16"),
+            Values("%short", "%half", "%short4", "%half4", "%mat4x4",
+                   "%short_buffer_block", "%half_buffer_block")));
+
+using ValidateSizedLoadStore =
+    spvtest::ValidateBase<std::tuple<std::string, uint32_t, std::string>>;
+
+CodeGenerator GetSizedLoadStoreCodeGenerator(const std::string& base_type,
+                                             uint32_t width) {
+  CodeGenerator generator;
+  generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n";
+  if (width == 8) {
+    generator.capabilities_ +=
+        "OpCapability UniformAndStorageBuffer8BitAccess\n";
+    generator.extensions_ = "OpExtension \"SPV_KHR_8bit_storage\"\n";
+  } else {
+    generator.capabilities_ +=
+        "OpCapability UniformAndStorageBuffer16BitAccess\n";
+    generator.extensions_ = "OpExtension \"SPV_KHR_16bit_storage\"\n";
+  }
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.before_types_ = R"(OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %struct 0 Offset 0
+)";
+  generator.types_ = R"(%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+)";
+
+  if (width == 8) {
+    generator.types_ += R"(%scalar = OpTypeInt 8 0
+%vector = OpTypeVector %scalar 4
+%struct = OpTypeStruct %vector
+)";
+  } else if (base_type == "int") {
+    generator.types_ += R"(%scalar = OpTypeInt 16 0
+%vector = OpTypeVector %scalar 4
+%struct = OpTypeStruct %vector
+)";
+  } else {
+    generator.types_ += R"(%scalar = OpTypeFloat 16
+%vector = OpTypeVector %scalar 4
+%matrix = OpTypeMatrix %vector 4
+%struct = OpTypeStruct %matrix
+%ptr_ssbo_matrix = OpTypePointer StorageBuffer %matrix
+)";
+    generator.before_types_ += R"(OpMemberDecorate %struct 0 RowMajor
+OpMemberDecorate %struct 0 MatrixStride 16
+)";
+  }
+  generator.types_ += R"(%block = OpTypeStruct %struct
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
+%ptr_ssbo_vector = OpTypePointer StorageBuffer %vector
+%ptr_ssbo_scalar = OpTypePointer StorageBuffer %scalar
+%ld_var = OpVariable %ptr_ssbo_block StorageBuffer
+%st_var = OpVariable %ptr_ssbo_block StorageBuffer
+)";
+
+  generator.after_types_ = R"(%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+)";
+  generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n";
+  return generator;
+}
+
+TEST_P(ValidateSizedLoadStore, Load) {
+  std::string base_type = std::get<0>(GetParam());
+  uint32_t width = std::get<1>(GetParam());
+  std::string mem_type = std::get<2>(GetParam());
+
+  CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width);
+  generator.after_types_ +=
+      "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0";
+  if (mem_type != "struct") {
+    generator.after_types_ += " %int_0";
+    if (mem_type != "matrix" && base_type == "float") {
+      generator.after_types_ += " %int_0";
+    }
+    if (mem_type == "scalar") {
+      generator.after_types_ += " %int_0";
+    }
+  }
+  generator.after_types_ += "\n";
+  generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n";
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  if (mem_type == "struct") {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "8- or 16-bit loads must be a scalar, vector or matrix type"));
+  } else {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  }
+}
+
+TEST_P(ValidateSizedLoadStore, Store) {
+  std::string base_type = std::get<0>(GetParam());
+  uint32_t width = std::get<1>(GetParam());
+  std::string mem_type = std::get<2>(GetParam());
+
+  CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width);
+  generator.after_types_ +=
+      "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0";
+  if (mem_type != "struct") {
+    generator.after_types_ += " %int_0";
+    if (mem_type != "matrix" && base_type == "float") {
+      generator.after_types_ += " %int_0";
+    }
+    if (mem_type == "scalar") {
+      generator.after_types_ += " %int_0";
+    }
+  }
+  generator.after_types_ += "\n";
+  generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n";
+  generator.after_types_ +=
+      "%st_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %st_var %int_0";
+  if (mem_type != "struct") {
+    generator.after_types_ += " %int_0";
+    if (mem_type != "matrix" && base_type == "float") {
+      generator.after_types_ += " %int_0";
+    }
+    if (mem_type == "scalar") {
+      generator.after_types_ += " %int_0";
+    }
+  }
+  generator.after_types_ += "\n";
+  generator.after_types_ += "OpStore %st_gep %ld\n";
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
+  if (mem_type == "struct") {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+    // Can only catch the load.
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "8- or 16-bit loads must be a scalar, vector or matrix type"));
+  } else {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(LoadStoreInt8, ValidateSizedLoadStore,
+                         Combine(Values("int"), Values(8u),
+                                 Values("scalar", "vector", "struct")));
+INSTANTIATE_TEST_SUITE_P(LoadStoreInt16, ValidateSizedLoadStore,
+                         Combine(Values("int"), Values(16u),
+                                 Values("scalar", "vector", "struct")));
+INSTANTIATE_TEST_SUITE_P(LoadStoreFloat16, ValidateSizedLoadStore,
+                         Combine(Values("float"), Values(16u),
+                                 Values("scalar", "vector", "matrix",
+                                        "struct")));
+
+TEST_F(ValidateMemory, SmallStorageCopyMemoryChar) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer8BitAccess
+OpExtension "SPV_KHR_8bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%char = OpTypeInt 8 0
+%block = OpTypeStruct %char
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%in = OpVariable %ptr_ssbo_block StorageBuffer
+%out = OpVariable %ptr_ssbo_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %out %in
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
+}
+
+TEST_F(ValidateMemory, SmallStorageCopyMemoryShort) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%short = OpTypeInt 16 0
+%block = OpTypeStruct %short
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%in = OpVariable %ptr_ssbo_block StorageBuffer
+%out = OpVariable %ptr_ssbo_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %out %in
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
+}
+
+TEST_F(ValidateMemory, SmallStorageCopyMemoryHalf) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability UniformAndStorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%half = OpTypeFloat 16
+%block = OpTypeStruct %half
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%in = OpVariable %ptr_ssbo_block StorageBuffer
+%out = OpVariable %ptr_ssbo_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpCopyMemory %out %in
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
+}
+
+TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockShort) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block BufferBlock
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%short = OpTypeInt 16 0
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%block = OpTypeStruct %short
+%block_array = OpTypeArray %block %int_4
+%ptr_block_array = OpTypePointer Uniform %block_array
+%var = OpVariable %ptr_block_array Uniform
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockChar) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer8BitAccess
+OpExtension "SPV_KHR_8bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block BufferBlock
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%char = OpTypeInt 8 0
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%block = OpTypeStruct %char
+%block_array = OpTypeArray %block %int_4
+%ptr_block_array = OpTypePointer Uniform %block_array
+%var = OpVariable %ptr_block_array Uniform
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockHalf) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+OpDecorate %block BufferBlock
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%half = OpTypeFloat 16
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%block = OpTypeStruct %half
+%block_array = OpTypeArray %block %int_4
+%ptr_block_array = OpTypePointer Uniform %block_array
+%var = OpVariable %ptr_block_array Uniform
+)";
+
+  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_misc_test.cpp b/test/val/val_misc_test.cpp
new file mode 100644
index 0000000..350f561
--- /dev/null
+++ b/test/val/val_misc_test.cpp
@@ -0,0 +1,88 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validation tests for misc instructions
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+using ValidateMisc = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateMisc, UndefRestrictedShort) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+%short = OpTypeInt 16 0
+%undef = OpUndef %short
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot create undefined values with 8- or 16-bit types"));
+}
+
+TEST_F(ValidateMisc, UndefRestrictedChar) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer8BitAccess
+OpExtension "SPV_KHR_8bit_storage"
+OpMemoryModel Logical GLSL450
+%char = OpTypeInt 8 0
+%undef = OpUndef %char
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot create undefined values with 8- or 16-bit types"));
+}
+
+TEST_F(ValidateMisc, UndefRestrictedHalf) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel Logical GLSL450
+%half = OpTypeFloat 16
+%undef = OpUndef %half
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cannot create undefined values with 8- or 16-bit types"));
+}
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 7f1ef093..688f433 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -510,7 +510,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GeometryRequiredModes, ValidateModeGeometry,
     Combine(Combine(Values("InputPoints", ""), Values("InputLines", ""),
                     Values("InputLinesAdjacency", ""), Values("Triangles", ""),
@@ -531,16 +531,24 @@
 
   std::ostringstream sstr;
   sstr << "OpCapability Shader\n";
-  sstr << "OpCapability Geometry\n";
-  sstr << "OpCapability Tessellation\n";
-  sstr << "OpCapability TransformFeedback\n";
-  if (!spvIsVulkanEnv(env)) {
+  if (!spvIsWebGPUEnv(env)) {
+    sstr << "OpCapability Geometry\n";
+    sstr << "OpCapability Tessellation\n";
+    sstr << "OpCapability TransformFeedback\n";
+  }
+  if (!spvIsVulkanOrWebGPUEnv(env)) {
     sstr << "OpCapability Kernel\n";
     if (env == SPV_ENV_UNIVERSAL_1_3) {
       sstr << "OpCapability SubgroupDispatch\n";
     }
   }
-  sstr << "OpMemoryModel Logical GLSL450\n";
+  if (spvIsWebGPUEnv(env)) {
+    sstr << "OpCapability VulkanMemoryModelKHR\n";
+    sstr << "OpExtension \"SPV_KHR_vulkan_memory_model\"\n";
+    sstr << "OpMemoryModel Logical VulkanKHR\n";
+  } else {
+    sstr << "OpMemoryModel Logical GLSL450\n";
+  }
   sstr << "OpEntryPoint " << model << " %main \"main\"\n";
   if (mode.find("LocalSizeId") == 0 || mode.find("LocalSizeHintId") == 0 ||
       mode.find("SubgroupsPerWorkgroupId") == 0) {
@@ -584,7 +592,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Geometry"),
             Values("Invocations 3", "InputPoints", "InputLines",
@@ -592,7 +600,7 @@
                    "OutputPoints", "OutputLineStrip", "OutputTriangleStrip"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with the Geometry "
@@ -604,7 +612,7 @@
                    "OutputPoints", "OutputLineStrip", "OutputTriangleStrip"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeTessellationOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""),
             Values("TessellationControl", "TessellationEvaluation"),
@@ -613,7 +621,7 @@
                    "PointMode", "Quads", "Isolines"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeTessellationOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a tessellation "
@@ -624,15 +632,15 @@
                    "PointMode", "Quads", "Isolines"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(ValidateModeGeometryAndTessellationGoodSpv10,
-                        ValidateModeExecution,
-                        Combine(Values(SPV_SUCCESS), Values(""),
-                                Values("TessellationControl",
-                                       "TessellationEvaluation", "Geometry"),
-                                Values("Triangles", "OutputVertices 3"),
-                                Values(SPV_ENV_UNIVERSAL_1_0)));
+INSTANTIATE_TEST_SUITE_P(ValidateModeGeometryAndTessellationGoodSpv10,
+                         ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("TessellationControl",
+                                        "TessellationEvaluation", "Geometry"),
+                                 Values("Triangles", "OutputVertices 3"),
+                                 Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryAndTessellationBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a Geometry or "
@@ -641,7 +649,7 @@
             Values("Triangles", "OutputVertices 3"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeFragmentOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Fragment"),
             Values("PixelCenterInteger", "OriginUpperLeft", "OriginLowerLeft",
@@ -649,7 +657,7 @@
                    "DepthUnchanged"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeFragmentOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with the Fragment "
@@ -657,19 +665,19 @@
             Values("Geometry", "TessellationControl", "TessellationEvaluation",
                    "GLCompute", "Vertex", "Kernel"),
             Values("PixelCenterInteger", "OriginUpperLeft", "OriginLowerLeft",
-                   "EarlyFragmentTests", "DepthReplacing", "DepthLess",
-                   "DepthUnchanged"),
+                   "EarlyFragmentTests", "DepthReplacing", "DepthGreater",
+                   "DepthLess", "DepthUnchanged"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(ValidateModeKernelOnlyGoodSpv13, ValidateModeExecution,
-                        Combine(Values(SPV_SUCCESS), Values(""),
-                                Values("Kernel"),
-                                Values("LocalSizeHint 1 1 1", "VecTypeHint 4",
-                                       "ContractionOff",
-                                       "LocalSizeHintId %int1"),
-                                Values(SPV_ENV_UNIVERSAL_1_3)));
+INSTANTIATE_TEST_SUITE_P(ValidateModeKernelOnlyGoodSpv13, ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("Kernel"),
+                                 Values("LocalSizeHint 1 1 1", "VecTypeHint 4",
+                                        "ContractionOff",
+                                        "LocalSizeHintId %int1"),
+                                 Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeKernelOnlyBadSpv13, ValidateModeExecution,
     Combine(
         Values(SPV_ERROR_INVALID_DATA),
@@ -681,13 +689,13 @@
                "LocalSizeHintId %int1"),
         Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGLComputeAndKernelGoodSpv13, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Kernel", "GLCompute"),
             Values("LocalSize 1 1 1", "LocalSizeId %int1 %int1 %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGLComputeAndKernelBadSpv13, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a Kernel or GLCompute "
@@ -697,7 +705,7 @@
             Values("LocalSize 1 1 1", "LocalSizeId %int1 %int1 %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeAllGoodSpv13, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""),
             Values("Kernel", "GLCompute", "Geometry", "TessellationControl",
@@ -706,6 +714,39 @@
                    "SubgroupsPerWorkgroup 1", "SubgroupsPerWorkgroupId %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
+INSTANTIATE_TEST_SUITE_P(ValidateModeGLComputeWebGPUWhitelistGood,
+                         ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("GLCompute"), Values("LocalSize 1 1 1"),
+                                 Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeGLComputeWebGPUWhitelistBad, ValidateModeExecution,
+    Combine(Values(SPV_ERROR_INVALID_DATA),
+            Values("Execution mode must be one of OriginUpperLeft, "
+                   "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                   "LocalSize, or LocalSizeHint for WebGPU environment"),
+            Values("GLCompute"), Values("LocalSizeId %int1 %int1 %int1"),
+            Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeFragmentWebGPUWhitelistGood, ValidateModeExecution,
+    Combine(Values(SPV_SUCCESS), Values(""), Values("Fragment"),
+            Values("OriginUpperLeft", "DepthReplacing", "DepthGreater",
+                   "DepthLess", "DepthUnchanged"),
+            Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeFragmentWebGPUWhitelistBad, ValidateModeExecution,
+    Combine(Values(SPV_ERROR_INVALID_DATA),
+            Values("Execution mode must be one of OriginUpperLeft, "
+                   "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                   "LocalSize, or LocalSizeHint for WebGPU environment"),
+            Values("Fragment"),
+            Values("PixelCenterInteger", "OriginLowerLeft",
+                   "EarlyFragmentTests"),
+            Values(SPV_ENV_WEBGPU_0)));
+
 TEST_F(ValidateModeExecution, MeshNVLocalSize) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -796,6 +837,347 @@
   EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
 }
 
+TEST_F(ValidateModeExecution, ExecModeSubgroupsPerWorkgroupIdBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionMode %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdSubgroupsPerWorkgroupIdGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdSubgroupsPerWorkgroupIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
+TEST_F(ValidateModeExecution, ExecModeLocalSizeHintIdBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionMode %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeHintIdGood) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionModeId %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeHintIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
+TEST_F(ValidateModeExecution, ExecModeLocalSizeIdBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionMode %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeIdGood) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockVertexBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionMode %main PixelInterlockOrderedEXT
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Execution mode can only be used with the Fragment execution model"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpCapability FragmentShaderSampleInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main PixelInterlockOrderedEXT
+OpExecutionMode %main SampleInterlockOrderedEXT
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one fragment shader interlock execution mode"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockNoModeBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entryf = OpLabel
+OpBeginInvocationInterlockEXT
+OpEndInvocationInterlockEXT
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpBeginInvocationInterlockEXT/OpEndInvocationInterlockEXT require a "
+          "fragment shader interlock execution mode"));
+}
+
+TEST_F(ValidateMode, FragmentShaderInterlockGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability FragmentShaderPixelInterlockEXT
+OpExtension "SPV_EXT_fragment_shader_interlock"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main PixelInterlockOrderedEXT
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entryf = OpLabel
+OpBeginInvocationInterlockEXT
+OpEndInvocationInterlockEXT
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMode, FragmentShaderDemoteVertexBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability DemoteToHelperInvocationEXT
+OpExtension "SPV_EXT_demote_to_helper_invocation"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+%bool = OpTypeBool
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpDemoteToHelperInvocationEXT
+%1 = OpIsHelperInvocationEXT %bool
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpDemoteToHelperInvocationEXT requires Fragment execution model"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpIsHelperInvocationEXT requires Fragment execution model"));
+}
+
+TEST_F(ValidateMode, FragmentShaderDemoteGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability DemoteToHelperInvocationEXT
+OpExtension "SPV_EXT_demote_to_helper_invocation"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%bool = OpTypeBool
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpDemoteToHelperInvocationEXT
+%1 = OpIsHelperInvocationEXT %bool
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMode, FragmentShaderDemoteBadType) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability DemoteToHelperInvocationEXT
+OpExtension "SPV_EXT_demote_to_helper_invocation"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%u32 = OpTypeInt 32 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpDemoteToHelperInvocationEXT
+%1 = OpIsHelperInvocationEXT %u32
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected bool scalar type as Result Type"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_non_uniform_test.cpp b/test/val/val_non_uniform_test.cpp
index a185d87..fbd11a9 100644
--- a/test/val/val_non_uniform_test.cpp
+++ b/test/val/val_non_uniform_test.cpp
@@ -182,63 +182,63 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformElect, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformElect"),
-                                Values("%bool"), ValuesIn(scopes), Values(""),
-                                Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformElect, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformElect"),
+                                 Values("%bool"), ValuesIn(scopes), Values(""),
+                                 Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformVote, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformAll",
-                                       "OpGroupNonUniformAny",
-                                       "OpGroupNonUniformAllEqual"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformVote, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformAll",
+                                        "OpGroupNonUniformAny",
+                                        "OpGroupNonUniformAllEqual"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBroadcast, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBroadcast"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcast, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcast"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBroadcastFirst, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBroadcastFirst"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcastFirst, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcastFirst"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallot, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallot"),
-                                Values("%u32vec4"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallot, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallot"),
+                                 Values("%u32vec4"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformInverseBallot, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformInverseBallot"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformInverseBallot, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformInverseBallot"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitExtract, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitExtract"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%u32vec4_null %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitExtract, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitExtract"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%u32vec4_null %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCount, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("Reduce %u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCount, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("Reduce %u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotFind, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotFindLSB",
-                                       "OpGroupNonUniformBallotFindMSB"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotFind, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotFindLSB",
+                                        "OpGroupNonUniformBallotFindMSB"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformShuffle, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformShuffle",
-                                       "OpGroupNonUniformShuffleXor",
-                                       "OpGroupNonUniformShuffleUp",
-                                       "OpGroupNonUniformShuffleDown"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32_0 %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformShuffle, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformShuffle",
+                                        "OpGroupNonUniformShuffleXor",
+                                        "OpGroupNonUniformShuffleUp",
+                                        "OpGroupNonUniformShuffleDown"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32_0 %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformIntegerArithmetic, GroupNonUniform,
     Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
                    "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
@@ -248,45 +248,45 @@
             Values("%u32"), ValuesIn(scopes), Values("Reduce %u32_0"),
             Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformFloatArithmetic, GroupNonUniform,
     Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
                    "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
             Values("%float"), ValuesIn(scopes), Values("Reduce %float_0"),
             Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformLogicalArithmetic, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformLogicalAnd",
-                                       "OpGroupNonUniformLogicalOr",
-                                       "OpGroupNonUniformLogicalXor"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("Reduce %true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformLogicalArithmetic, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformLogicalAnd",
+                                        "OpGroupNonUniformLogicalOr",
+                                        "OpGroupNonUniformLogicalXor"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("Reduce %true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformQuad, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformQuadBroadcast",
-                                       "OpGroupNonUniformQuadSwap"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32_0 %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformQuad, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformQuadBroadcast",
+                                        "OpGroupNonUniformQuadSwap"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32_0 %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCountScope, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("Reduce %u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCountScope, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("Reduce %u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformBallotBitCountBadResultType, GroupNonUniform,
     Combine(
         Values("OpGroupNonUniformBallotBitCount"), Values("%float", "%int"),
         Values(SpvScopeSubgroup), Values("Reduce %u32vec4_null"),
         Values("Expected Result Type to be an unsigned integer type scalar.")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCountBadValue, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), Values(SpvScopeSubgroup),
-                                Values("Reduce %u32vec3_null", "Reduce %u32_0",
-                                       "Reduce %float_0"),
-                                Values("Expected Value to be a vector of four "
-                                       "components of integer type scalar")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCountBadValue, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), Values(SpvScopeSubgroup),
+                                 Values("Reduce %u32vec3_null", "Reduce %u32_0",
+                                        "Reduce %float_0"),
+                                 Values("Expected Value to be a vector of four "
+                                        "components of integer type scalar")));
 
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_opencl_test.cpp b/test/val/val_opencl_test.cpp
new file mode 100644
index 0000000..18b2f71
--- /dev/null
+++ b/test/val/val_opencl_test.cpp
@@ -0,0 +1,272 @@
+// Copyright (c) 2019 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.
+
+// Validation tests for OpenCL env specific checks
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using testing::HasSubstr;
+
+using ValidateOpenCL = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateOpenCL, NonPhysicalAddressingModelBad) {
+  std::string spirv = R"(
+     OpCapability Kernel
+     OpMemoryModel Logical OpenCL
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Addressing model must be Physical32 or Physical64 "
+                        "in the OpenCL environment.\n  OpMemoryModel Logical "
+                        "OpenCL\n"));
+}
+
+TEST_F(ValidateOpenCL, NonOpenCLMemoryModelBad) {
+  std::string spirv = R"(
+     OpCapability Kernel
+     OpMemoryModel Physical32 GLSL450
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Memory model must be OpenCL in the OpenCL environment."
+                        "\n  OpMemoryModel Physical32 GLSL450\n"));
+}
+
+TEST_F(ValidateOpenCL, NonVoidSampledTypeImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeInt 32 0
+    %2 = OpTypeImage %1 2D 0 0 0 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Sampled Type must be OpTypeVoid in the OpenCL environment."
+                "\n  %2 = OpTypeImage %uint 2D 0 0 0 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NonZeroMSImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 2D 0 0 1 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("MS must be 0 in the OpenCL environement."
+                "\n  %2 = OpTypeImage %void 2D 0 0 1 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, Non1D2DArrayedImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 1 0 0 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("In the OpenCL environment, Arrayed may only be set to 1 "
+                "when Dim is either 1D or 2D."
+                "\n  %2 = OpTypeImage %void 3D 0 1 0 0 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NonZeroSampledImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 0 0 1 Unknown ReadOnly
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Sampled must be 0 in the OpenCL environment."
+                "\n  %2 = OpTypeImage %void 3D 0 0 0 1 Unknown ReadOnly\n"));
+}
+
+TEST_F(ValidateOpenCL, NoAccessQualifierImageBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpMemoryModel Physical32 OpenCL
+    %1 = OpTypeVoid
+    %2 = OpTypeImage %1 3D 0 0 0 0 Unknown
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In the OpenCL environment, the optional "
+                        "Access Qualifier must be present."
+                        "\n  %2 = OpTypeImage %void 3D 0 0 0 0 Unknown\n"));
+}
+
+TEST_F(ValidateOpenCL, ImageWriteWithOptionalImageOperandsBad) {
+  std::string spirv = R"(
+    OpCapability Addresses
+    OpCapability Kernel
+    OpCapability ImageBasic
+    OpMemoryModel Physical64 OpenCL
+    OpEntryPoint Kernel %5 "test"
+    %uint = OpTypeInt 32 0
+    %uint_7 = OpConstant %uint 7
+    %uint_3 = OpConstant %uint 3
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+    %uint_4 = OpConstant %uint 4
+    %void = OpTypeVoid
+    %3 = OpTypeImage %void 2D 0 0 0 0 Unknown WriteOnly
+    %4 = OpTypeFunction %void %3
+    %v2uint = OpTypeVector %uint 2
+    %v4uint = OpTypeVector %uint 4
+    %12 = OpConstantComposite %v2uint %uint_7 %uint_3
+    %17 = OpConstantComposite %v4uint %uint_1 %uint_2 %uint_3 %uint_4
+    %5 = OpFunction %void None %4
+    %img = OpFunctionParameter %3
+    %entry = OpLabel
+    OpImageWrite %img %12 %17 ConstOffset %12
+    OpReturn
+    OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Optional Image Operands are not allowed in the "
+                        "OpenCL environment."
+                        "\n  OpImageWrite %15 %13 %14 ConstOffset %13\n"));
+}
+
+TEST_F(ValidateOpenCL, ImageReadWithConstOffsetBad) {
+  std::string spirv = R"(
+               OpCapability Addresses
+               OpCapability Kernel
+               OpCapability ImageBasic
+               OpMemoryModel Physical64 OpenCL
+               OpEntryPoint Kernel %5 "image_kernel"
+               OpName %img "img"
+               OpName %coord "coord"
+               OpName %call "call"
+       %uint = OpTypeInt 32 0
+     %uint_7 = OpConstant %uint 7
+     %uint_3 = OpConstant %uint 3
+       %void = OpTypeVoid
+          %3 = OpTypeImage %void 2D 0 0 0 0 Unknown ReadOnly
+          %4 = OpTypeFunction %void %3
+     %v4uint = OpTypeVector %uint 4
+     %v2uint = OpTypeVector %uint 2
+      %coord = OpConstantComposite %v2uint %uint_7 %uint_3
+          %5 = OpFunction %void None %4
+        %img = OpFunctionParameter %3
+      %entry = OpLabel
+       %call = OpImageRead %v4uint %img %coord ConstOffset %coord
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "ConstOffset image operand not allowed in the OpenCL environment."
+          "\n  %call = OpImageRead %v4uint %img %coord ConstOffset %coord\n"));
+}
+
+TEST_F(ValidateOpenCL, ImageSampleExplicitLodWithConstOffsetBad) {
+  std::string spirv = R"(
+               OpCapability Addresses
+               OpCapability Kernel
+               OpCapability ImageBasic
+               OpCapability LiteralSampler
+               OpMemoryModel Physical64 OpenCL
+               OpEntryPoint Kernel %5 "image_kernel"
+               OpName %img "img"
+               OpName %coord "coord"
+               OpName %call "call"
+       %uint = OpTypeInt 32 0
+     %uint_7 = OpConstant %uint 7
+     %uint_3 = OpConstant %uint 3
+       %void = OpTypeVoid
+          %3 = OpTypeImage %void 2D 0 0 0 0 Unknown ReadOnly
+          %4 = OpTypeFunction %void %3
+          %8 = OpTypeSampler
+         %10 = OpTypeSampledImage %3
+     %v4uint = OpTypeVector %uint 4
+     %v2uint = OpTypeVector %uint 2
+      %float = OpTypeFloat 32
+          %9 = OpConstantSampler %8 None 0 Nearest
+      %coord = OpConstantComposite %v2uint %uint_7 %uint_3
+    %float_0 = OpConstant %float 0
+          %5 = OpFunction %void None %4
+          %6 = OpFunctionParameter %3
+      %entry = OpLabel
+        %img = OpSampledImage %10 %6 %9
+       %call = OpImageSampleExplicitLod %v4uint %img %coord
+                                        Lod|ConstOffset %float_0 %coord
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "ConstOffset image operand not allowed in the OpenCL environment."
+          "\n  %call = OpImageSampleExplicitLod %v4uint %img "
+          "%coord Lod|ConstOffset %float_0 %coord\n"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_small_type_uses_test.cpp b/test/val/val_small_type_uses_test.cpp
new file mode 100644
index 0000000..b950af5
--- /dev/null
+++ b/test/val/val_small_type_uses_test.cpp
@@ -0,0 +1,338 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validation tests for 8- and 16-bit type uses.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateSmallTypeUses = spvtest::ValidateBase<bool>;
+
+CodeGenerator GetSmallTypesGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = R"(
+OpCapability Shader
+OpCapability StorageBuffer16BitAccess
+OpCapability StorageBuffer8BitAccess
+)";
+  generator.extensions_ = R"(
+OpExtension "SPV_KHR_16bit_storage"
+OpExtension "SPV_KHR_8bit_storage"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%ext = OpExtInstImport "GLSL.std.450"
+)";
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  std::string body = R"(
+%short_gep = OpAccessChain %ptr_ssbo_short %var %int_0 %int_0
+%ld_short = OpLoad %short %short_gep
+%short_to_int = OpSConvert %int %ld_short
+%short_to_uint = OpUConvert %int %ld_short
+%short_to_char = OpSConvert %char %ld_short
+%short_to_uchar = OpSConvert %char %ld_short
+%short2_gep = OpAccessChain %ptr_ssbo_short2 %var %int_0
+%ld_short2 = OpLoad %short2 %short2_gep
+%short2_to_int2 = OpSConvert %int2 %ld_short2
+%short2_to_uint2 = OpUConvert %int2 %ld_short2
+%short2_to_char2 = OpSConvert %char2 %ld_short2
+%short2_to_uchar2 = OpSConvert %char2 %ld_short2
+
+%char_gep = OpAccessChain %ptr_ssbo_char %var %int_2 %int_0
+%ld_char = OpLoad %char %char_gep
+%char_to_int = OpSConvert %int %ld_char
+%char_to_uint = OpUConvert %int %ld_char
+%char_to_short = OpSConvert %short %ld_char
+%char_to_ushort = OpSConvert %short %ld_char
+%char2_gep = OpAccessChain %ptr_ssbo_char2 %var %int_2
+%ld_char2 = OpLoad %char2 %char2_gep
+%char2_to_int2 = OpSConvert %int2 %ld_char2
+%char2_to_uint2 = OpUConvert %int2 %ld_char2
+%char2_to_short2 = OpSConvert %short2 %ld_char2
+%char2_to_ushort2 = OpSConvert %short2 %ld_char2
+
+%half_gep = OpAccessChain %ptr_ssbo_half %var %int_1 %int_0
+%ld_half = OpLoad %half %half_gep
+%half_to_float = OpFConvert %float %ld_half
+%half2_gep = OpAccessChain %ptr_ssbo_half2 %var %int_1
+%ld_half2 = OpLoad %half2 %half2_gep
+%half2_to_float2 = OpFConvert %float2 %ld_half2
+
+%int_to_short = OpSConvert %short %int_0
+%int_to_ushort = OpUConvert %short %int_0
+%int_to_char = OpSConvert %char %int_0
+%int_to_uchar = OpUConvert %char %int_0
+%int2_to_short2 = OpSConvert %short2 %int2_0
+%int2_to_ushort2 = OpUConvert %short2 %int2_0
+%int2_to_char2 = OpSConvert %char2 %int2_0
+%int2_to_uchar2 = OpUConvert %char2 %int2_0
+%int_gep = OpAccessChain %ptr_ssbo_int %var %int_3 %int_0
+%int2_gep = OpAccessChain %ptr_ssbo_int2 %var %int_3
+
+%float_to_half = OpFConvert %half %float_0
+%float2_to_half2 = OpFConvert %half2 %float2_0
+%float_gep = OpAccessChain %ptr_ssbo_float %var %int_4 %int_0
+%float2_gep = OpAccessChain %ptr_ssbo_float2 %var %int_4
+)";
+  generator.entry_points_.push_back(
+      {"foo", "GLCompute", "OpExecutionMode %foo LocalSize 1 1 1", body, ""});
+  generator.before_types_ = R"(
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 1 Offset 8
+OpMemberDecorate %block 2 Offset 16
+OpMemberDecorate %block 3 Offset 32
+OpMemberDecorate %block 4 Offset 64
+)";
+  generator.types_ = R"(
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int2 = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%bool = OpTypeBool
+%bool2 = OpTypeVector %bool 2
+%char = OpTypeInt 8 0
+%char2 = OpTypeVector %char 2
+%ptr_ssbo_char = OpTypePointer StorageBuffer %char
+%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2
+%short = OpTypeInt 16 0
+%short2 = OpTypeVector %short 2
+%ptr_ssbo_short = OpTypePointer StorageBuffer %short
+%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2
+%half = OpTypeFloat 16
+%half2 = OpTypeVector %half 2
+%ptr_ssbo_half = OpTypePointer StorageBuffer %half
+%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_ssbo_int2 = OpTypePointer StorageBuffer %int2
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%ptr_ssbo_float2 = OpTypePointer StorageBuffer %float2
+%block = OpTypeStruct %short2 %half2 %char2 %int2 %float2
+%ptr_ssbo_block = OpTypePointer StorageBuffer %block
+%func = OpTypeFunction %void
+)";
+  generator.after_types_ = R"(
+%var = OpVariable %ptr_ssbo_block StorageBuffer
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int2_0 = OpConstantComposite %int2 %int_0 %int_0
+%float_0 = OpConstant %float 0
+%float2_0 = OpConstantComposite %float2 %float_0 %float_0
+
+%short_func_ty = OpTypeFunction %void %short
+%char_func_ty = OpTypeFunction %void %char
+%half_func_ty = OpTypeFunction %void %half
+)";
+  generator.add_at_the_end_ = R"(
+%short_func = OpFunction %void None %short_func_ty
+%short_param = OpFunctionParameter %short
+%short_func_entry = OpLabel
+OpReturn
+OpFunctionEnd
+%char_func = OpFunction %void None %char_func_ty
+%char_param = OpFunctionParameter %char
+%char_func_entry = OpLabel
+OpReturn
+OpFunctionEnd
+%half_func = OpFunction %void None %half_func_ty
+%half_param = OpFunctionParameter %half
+%half_func_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return generator;
+}
+
+TEST_F(ValidateSmallTypeUses, BadCharPhi) {
+  CodeGenerator generator = GetSmallTypesGenerator();
+  generator.entry_points_[0].body += R"(
+OpBranch %next_block
+%next_block = OpLabel
+%phi = OpPhi %char %ld_char %foo_entry
+)";
+
+  CompileSuccessfully(generator.Build());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid use of 8- or 16-bit result"));
+}
+
+TEST_F(ValidateSmallTypeUses, BadShortPhi) {
+  CodeGenerator generator = GetSmallTypesGenerator();
+  generator.entry_points_[0].body += R"(
+OpBranch %next_block
+%next_block = OpLabel
+%phi = OpPhi %short %ld_short %foo_entry
+)";
+
+  CompileSuccessfully(generator.Build());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid use of 8- or 16-bit result"));
+}
+
+TEST_F(ValidateSmallTypeUses, BadHalfPhi) {
+  CodeGenerator generator = GetSmallTypesGenerator();
+  generator.entry_points_[0].body += R"(
+OpBranch %next_block
+%next_block = OpLabel
+%phi = OpPhi %half %ld_half %foo_entry
+)";
+
+  CompileSuccessfully(generator.Build());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid use of 8- or 16-bit result"));
+}
+
+using ValidateGoodUses = spvtest::ValidateBase<std::string>;
+
+TEST_P(ValidateGoodUses, Inst) {
+  const std::string inst = GetParam();
+  CodeGenerator generator = GetSmallTypesGenerator();
+  generator.entry_points_[0].body += inst + "\n";
+
+  CompileSuccessfully(generator.Build());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SmallTypeUsesValid, ValidateGoodUses,
+    Values(
+        "%inst = OpIAdd %int %short_to_int %int_0",
+        "%inst = OpIAdd %int %short_to_uint %int_0",
+        "%inst = OpIAdd %int2 %short2_to_int2 %int2_0",
+        "%inst = OpIAdd %int2 %short2_to_uint2 %int2_0",
+        "%inst = OpIAdd %int %char_to_int %int_0",
+        "%inst = OpIAdd %int %char_to_uint %int_0",
+        "%inst = OpIAdd %int2 %char2_to_int2 %int2_0",
+        "%inst = OpIAdd %int2 %char2_to_uint2 %int2_0",
+        "%inst = OpUConvert %int %ld_short",
+        "%inst = OpSConvert %int %ld_short",
+        "%inst = OpUConvert %char %ld_short",
+        "%inst = OpSConvert %char %ld_short",
+        "%inst = OpUConvert %int %ld_char", "%inst = OpSConvert %int %ld_char",
+        "%inst = OpUConvert %short %ld_char",
+        "%inst = OpSConvert %short %ld_char",
+        "%inst = OpUConvert %int2 %ld_short2",
+        "%inst = OpSConvert %int2 %ld_short2",
+        "%inst = OpUConvert %char2 %ld_short2",
+        "%inst = OpSConvert %char2 %ld_short2",
+        "%inst = OpUConvert %int2 %ld_char2",
+        "%inst = OpSConvert %int2 %ld_char2",
+        "%inst = OpUConvert %short2 %ld_char2",
+        "%inst = OpSConvert %short2 %ld_char2",
+        "OpStore %short_gep %int_to_short", "OpStore %short_gep %int_to_ushort",
+        "OpStore %short_gep %char_to_short",
+        "OpStore %short_gep %char_to_ushort",
+        "OpStore %short2_gep %int2_to_short2",
+        "OpStore %short2_gep %int2_to_ushort2",
+        "OpStore %short2_gep %char2_to_short2",
+        "OpStore %short2_gep %char2_to_ushort2",
+        "OpStore %char_gep %int_to_char", "OpStore %char_gep %int_to_uchar",
+        "OpStore %char_gep %short_to_char", "OpStore %char_gep %short_to_uchar",
+        "OpStore %char2_gep %int2_to_char2",
+        "OpStore %char2_gep %int2_to_uchar2",
+        "OpStore %char2_gep %short2_to_char2",
+        "OpStore %char2_gep %short2_to_uchar2",
+        "OpStore %int_gep %short_to_int", "OpStore %int_gep %short_to_uint",
+        "OpStore %int_gep %char_to_int", "OpStore %int2_gep %char2_to_uint2",
+        "OpStore %int2_gep %short2_to_int2",
+        "OpStore %int2_gep %short2_to_uint2",
+        "OpStore %int2_gep %char2_to_int2", "OpStore %int2_gep %char2_to_uint2",
+        "%inst = OpFAdd %float %half_to_float %float_0",
+        "%inst = OpFAdd %float2 %half2_to_float2 %float2_0",
+        "%inst = OpFConvert %float %ld_half",
+        "%inst = OpFConvert %float2 %ld_half2",
+        "OpStore %half_gep %float_to_half", "OpStore %half_gep %ld_half",
+        "OpStore %half2_gep %float2_to_half2", "OpStore %half2_gep %ld_half2",
+        "OpStore %float_gep %half_to_float",
+        "OpStore %float2_gep %half2_to_float2"));
+
+using ValidateBadUses = spvtest::ValidateBase<std::string>;
+
+TEST_P(ValidateBadUses, Inst) {
+  const std::string inst = GetParam();
+  CodeGenerator generator = GetSmallTypesGenerator();
+  generator.entry_points_[0].body += inst + "\n";
+
+  CompileSuccessfully(generator.Build());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid use of 8- or 16-bit result"));
+}
+
+// A smattering of unacceptable use cases. Far too vast to cover exhaustively.
+INSTANTIATE_TEST_SUITE_P(
+    SmallTypeUsesInvalid, ValidateBadUses,
+    Values("%inst = OpIAdd %short %ld_short %ld_short",
+           "%inst = OpIAdd %short %char_to_short %char_to_short",
+           "%inst = OpIAdd %short %char_to_ushort %char_to_ushort",
+           "%inst = OpIAdd %short %int_to_short %int_to_short",
+           "%inst = OpIAdd %short %int_to_ushort %int_to_ushort",
+           "%inst = OpIAdd %short2 %ld_short2 %ld_short2",
+           "%inst = OpIAdd %short2 %char2_to_short2 %char2_to_short2",
+           "%inst = OpIAdd %short2 %char2_to_ushort2 %char2_to_ushort2",
+           "%inst = OpIAdd %short2 %int2_to_short2 %int2_to_short2",
+           "%inst = OpIAdd %short2 %int2_to_ushort2 %int2_to_ushort2",
+           "%inst = OpIEqual %bool %ld_short %ld_short",
+           "%inst = OpIEqual %bool %char_to_short %char_to_short",
+           "%inst = OpIEqual %bool %char_to_ushort %char_to_ushort",
+           "%inst = OpIEqual %bool %int_to_short %int_to_short",
+           "%inst = OpIEqual %bool %int_to_ushort %int_to_ushort",
+           "%inst = OpIEqual %bool2 %ld_short2 %ld_short2",
+           "%inst = OpIEqual %bool2 %char2_to_short2 %char2_to_short2",
+           "%inst = OpIEqual %bool2 %char2_to_ushort2 %char2_to_ushort2",
+           "%inst = OpIEqual %bool2 %int2_to_short2 %int2_to_short2",
+           "%inst = OpIEqual %bool2 %int2_to_ushort2 %int2_to_ushort2",
+           "%inst = OpFAdd %half %ld_half %ld_half",
+           "%inst = OpFAdd %half %float_to_half %float_to_half",
+           "%inst = OpFAdd %half2 %ld_half2 %ld_half2",
+           "%inst = OpFAdd %half2 %float2_to_half2 %float2_to_half2",
+           "%inst = OpFOrdGreaterThan %bool %ld_half %ld_half",
+           "%inst = OpFOrdGreaterThan %bool %float_to_half %float_to_half",
+           "%inst = OpFOrdGreaterThan %bool2 %ld_half2 %ld_half2",
+           "%inst = OpFOrdGreaterThan %bool2 %float2_to_half2 %float2_to_half2",
+           "%inst = OpFunctionCall %void %short_func %ld_short",
+           "%inst = OpFunctionCall %void %short_func %char_to_short",
+           "%inst = OpFunctionCall %void %short_func %char_to_ushort",
+           "%inst = OpFunctionCall %void %short_func %int_to_short",
+           "%inst = OpFunctionCall %void %short_func %int_to_ushort",
+           "%inst = OpFunctionCall %void %char_func %ld_char",
+           "%inst = OpFunctionCall %void %char_func %short_to_char",
+           "%inst = OpFunctionCall %void %char_func %short_to_uchar",
+           "%inst = OpFunctionCall %void %char_func %int_to_char",
+           "%inst = OpFunctionCall %void %char_func %int_to_uchar",
+           "%inst = OpFunctionCall %void %half_func %ld_half",
+           "%inst = OpFunctionCall %void %half_func %float_to_half"));
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_ssa_test.cpp b/test/val/val_ssa_test.cpp
index 5d8fa4b..e10abd7 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -823,7 +823,7 @@
     {"OpGetKernelWorkGroupSize", kNoNDrange},
     {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};
 
-INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), );
+INSTANTIATE_TEST_SUITE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases));
 
 static const std::string return_instructions = R"(
   OpReturn
diff --git a/test/val/val_storage_test.cpp b/test/val/val_storage_test.cpp
index aa1eecd..f54b425 100644
--- a/test/val/val_storage_test.cpp
+++ b/test/val/val_storage_test.cpp
@@ -26,7 +26,10 @@
 namespace {
 
 using ::testing::HasSubstr;
+using ::testing::Values;
 using ValidateStorage = spvtest::ValidateBase<std::string>;
+using ValidateStorageClass =
+    spvtest::ValidateBase<std::tuple<std::string, bool, bool, std::string>>;
 
 TEST_F(ValidateStorage, FunctionStorageInsideFunction) {
   char str[] = R"(
@@ -137,7 +140,7 @@
       "Variables must have a function[7] storage class inside of a function"));
 }
 
-INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateStorage,
+INSTANTIATE_TEST_SUITE_P(MatrixOp, ValidateStorage,
                         ::testing::Values(
                              "Input",
                              "Uniform",
@@ -147,7 +150,7 @@
                              "Private",
                              "PushConstant",
                              "AtomicCounter",
-                             "Image"),);
+                             "Image"));
 // clang-format on
 
 TEST_F(ValidateStorage, GenericVariableOutsideFunction) {
@@ -186,6 +189,131 @@
               HasSubstr("OpVariable storage class cannot be Generic"));
 }
 
+TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParam) {
+  const auto str = R"(
+          OpCapability Shader
+          OpCapability Linkage
+          OpMemoryModel Logical GLSL450
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%ptrt   = OpTypePointer Function %intt
+%vfunct = OpTypeFunction %voidt
+%vifunct = OpTypeFunction %voidt %ptrt
+%wgroupptrt = OpTypePointer Workgroup %intt
+%wgroup = OpVariable %wgroupptrt Workgroup
+%main   = OpFunction %voidt None %vfunct
+%mainl  = OpLabel
+%ret    = OpFunctionCall %voidt %func %wgroup
+          OpReturn
+          OpFunctionEnd
+%func   = OpFunction %voidt None %vifunct
+%arg    = OpFunctionParameter %ptrt
+%funcl  = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  getValidatorOptions()->before_hlsl_legalization = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParamBad) {
+  const auto str = R"(
+          OpCapability Shader
+          OpCapability Linkage
+          OpMemoryModel Logical GLSL450
+%floatt = OpTypeFloat 32
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%ptrt   = OpTypePointer Function %intt
+%vfunct = OpTypeFunction %voidt
+%vifunct = OpTypeFunction %voidt %ptrt
+%wgroupptrt = OpTypePointer Workgroup %floatt
+%wgroup = OpVariable %wgroupptrt Workgroup
+%main   = OpFunction %voidt None %vfunct
+%mainl  = OpLabel
+%ret    = OpFunctionCall %voidt %func %wgroup
+          OpReturn
+          OpFunctionEnd
+%func   = OpFunction %voidt None %vifunct
+%arg    = OpFunctionParameter %ptrt
+%funcl  = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  getValidatorOptions()->relax_logical_pointer = true;
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpFunctionCall Argument <id> '"));
+}
+
+std::string GetVarDeclStr(const std::string& storage_class) {
+  if (storage_class != "Output" && storage_class != "Private" &&
+      storage_class != "Function") {
+    return "%var    = OpVariable %ptrt " + storage_class + "\n";
+  } else {
+    return "%var    = OpVariable %ptrt " + storage_class + " %null\n";
+  }
+}
+
+TEST_P(ValidateStorageClass, WebGPU) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool is_local = std::get<1>(GetParam());
+  bool is_valid = std::get<2>(GetParam());
+  std::string error = std::get<3>(GetParam());
+
+  std::string str = R"(
+          OpCapability Shader
+          OpCapability VulkanMemoryModelKHR
+          OpExtension "SPV_KHR_vulkan_memory_model"
+          OpMemoryModel Logical VulkanKHR
+          OpEntryPoint Fragment %func "func"
+          OpExecutionMode %func OriginUpperLeft
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%vfunct = OpTypeFunction %voidt
+%null   = OpConstantNull %intt
+)";
+  str += "%ptrt   = OpTypePointer " + storage_class + " %intt\n";
+  if (!is_local) str += GetVarDeclStr(storage_class);
+  str += R"(
+%func   = OpFunction %voidt None %vfunct
+%funcl  = OpLabel
+)";
+  if (is_local) str += GetVarDeclStr(storage_class);
+  str += R"(
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  if (is_valid) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(error));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    StorageClass, ValidateStorageClass,
+    Values(std::make_tuple("UniformConstant", false, true, ""),
+           std::make_tuple("Uniform", false, true, ""),
+           std::make_tuple("StorageBuffer", false, true, ""),
+           std::make_tuple("Input", false, true, ""),
+           std::make_tuple("Output", false, true, ""),
+           std::make_tuple("Image", false, true, ""),
+           std::make_tuple("Workgroup", false, true, ""),
+           std::make_tuple("Private", false, true, ""),
+           std::make_tuple("Function", true, true, ""),
+           std::make_tuple(
+               "CrossWorkgroup", false, false,
+               "For WebGPU, OpTypePointer storage class must be one of"),
+           std::make_tuple(
+               "PushConstant", false, false,
+               "For WebGPU, OpTypePointer storage class must be one of")));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_validation_state_test.cpp b/test/val/val_validation_state_test.cpp
index bf65094..4581579 100644
--- a/test/val/val_validation_state_test.cpp
+++ b/test/val/val_validation_state_test.cpp
@@ -56,15 +56,16 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%null = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -89,7 +90,7 @@
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
 %2 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturn
 OpFunctionEnd
 )";
 
@@ -100,16 +101,17 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%null = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
 %14 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -303,7 +305,7 @@
             ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("For WebGPU, functions need to be defined before being "
-                        "called.\n  %9 = OpFunctionCall %_struct_5 %10\n"));
+                        "called.\n  %10 = OpFunctionCall %_struct_5 %11\n"));
 }
 
 TEST_F(ValidationStateTest,
diff --git a/test/val/val_version_test.cpp b/test/val/val_version_test.cpp
index eb9bbb9..2b9542a 100644
--- a/test/val/val_version_test.cpp
+++ b/test/val/val_version_test.cpp
@@ -56,9 +56,10 @@
 )";
 
 const std::string opencl_spirv = R"(
+OpCapability Addresses
 OpCapability Kernel
 OpCapability Linkage
-OpMemoryModel Logical OpenCL
+OpMemoryModel Physical32 OpenCL
 )";
 
 std::string version(spv_target_env env) {
@@ -86,6 +87,9 @@
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
       return "1.3";
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return "1.4";
     default:
       return "0";
   }
@@ -108,7 +112,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(Universal, ValidateVersion,
   ::testing::Values(
     //         Binary version,        Target environment
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true),
@@ -165,7 +169,7 @@
   )
 );
 
-INSTANTIATE_TEST_CASE_P(Vulkan, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(Vulkan, ValidateVersion,
   ::testing::Values(
     //         Binary version,        Target environment
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true),
@@ -179,6 +183,7 @@
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_2,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_5,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
 
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
@@ -190,11 +195,12 @@
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
-    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false)
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true)
   )
 );
 
-INSTANTIATE_TEST_CASE_P(OpenCL, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(OpenCL, ValidateVersion,
   ::testing::Values(
     //         Binary version,     Target environment
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_0,       opencl_spirv, true),
@@ -247,7 +253,7 @@
   )
 );
 
-INSTANTIATE_TEST_CASE_P(OpenCLEmbedded, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(OpenCLEmbedded, ValidateVersion,
   ::testing::Values(
     //         Binary version,              Target environment
     std::make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_0,       opencl_spirv, true),
diff --git a/test/val/val_webgpu_test.cpp b/test/val/val_webgpu_test.cpp
index 48ea21d..c55f927 100644
--- a/test/val/val_webgpu_test.cpp
+++ b/test/val/val_webgpu_test.cpp
@@ -276,6 +276,63 @@
                         "\"SPV_KHR_8bit_storage\"\n"));
 }
 
+spv_binary GenerateTrivialBinary(bool need_little_endian) {
+  // Smallest possible valid WebGPU SPIR-V binary in little endian. Contains all
+  // the required boilerplate and a trivial entry point function.
+  static const uint8_t binary_bytes[] = {
+      // clang-format off
+    0x03, 0x02, 0x23, 0x07, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00,
+    0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0xE1, 0x14, 0x00, 0x00,
+    0x0A, 0x00, 0x08, 0x00, 0x53, 0x50, 0x56, 0x5F, 0x4B, 0x48, 0x52, 0x5F,
+    0x76, 0x75, 0x6C, 0x6B, 0x61, 0x6E, 0x5F, 0x6D, 0x65, 0x6D, 0x6F, 0x72,
+    0x79, 0x5F, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x00, 0x0E, 0x00, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x05, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x73, 0x68, 0x61, 0x64,
+    0x65, 0x72, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+    0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+    0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
+    0x04, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+      // clang-format on
+  };
+  static const size_t word_count = sizeof(binary_bytes) / sizeof(uint32_t);
+  std::unique_ptr<spv_binary_t> result(new spv_binary_t);
+  if (!result) return nullptr;
+
+  result->wordCount = word_count;
+  result->code = new uint32_t[word_count];
+  if (!result->code) return nullptr;
+
+  if (need_little_endian) {
+    memcpy(result->code, binary_bytes, sizeof(binary_bytes));
+  } else {
+    uint8_t* code_bytes = reinterpret_cast<uint8_t*>(result->code);
+    for (size_t word = 0; word < word_count; ++word) {
+      code_bytes[4 * word] = binary_bytes[4 * word + 3];
+      code_bytes[4 * word + 1] = binary_bytes[4 * word + 2];
+      code_bytes[4 * word + 2] = binary_bytes[4 * word + 1];
+      code_bytes[4 * word + 3] = binary_bytes[4 * word];
+    }
+  }
+
+  return result.release();
+}
+
+TEST_F(ValidateWebGPU, LittleEndianGood) {
+  DestroyBinary();
+  binary_ = GenerateTrivialBinary(true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateWebGPU, BigEndianBad) {
+  DestroyBinary();
+  binary_ = GenerateTrivialBinary(false);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU requires SPIR-V to be little endian."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 9fb3a91..b3a4cc1 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -12,7 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-add_subdirectory(lesspipe)
+if (NOT ${SPIRV_SKIP_EXECUTABLES})
+ add_subdirectory(lesspipe)
+endif()
 add_subdirectory(emacs)
 
 # Add a SPIR-V Tools command line tool. Signature:
@@ -42,15 +44,10 @@
   add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
+    add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  endif()
   add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-stats
-	            SRCS stats/stats.cpp
-		               stats/stats_analyzer.cpp
-                   stats/stats_analyzer.h
-                   stats/spirv_stats.cpp
-                   stats/spirv_stats.h
-		    LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-cfg
                     SRCS cfg/cfg.cpp
                          cfg/bin_to_dot.h
@@ -58,22 +55,16 @@
                     LIBS ${SPIRV_TOOLS})
   target_include_directories(spirv-cfg PRIVATE ${spirv-tools_SOURCE_DIR}
                                                ${SPIRV_HEADER_INCLUDE_DIR})
-  target_include_directories(spirv-stats PRIVATE ${spirv-tools_SOURCE_DIR}
-                                                 ${SPIRV_HEADER_INCLUDE_DIR})
+  set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt
+                            spirv-cfg spirv-link)
+  if(NOT DEFINED IOS_PLATFORM)
+    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
+  endif()
 
-  set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
-                            spirv-cfg spirv-link spirv-reduce)
-
-  if(SPIRV_BUILD_COMPRESSION)
-    add_spvtools_tool(TARGET spirv-markv
-                      SRCS comp/markv.cpp
-                           comp/markv_model_factory.cpp
-                           comp/markv_model_shader.cpp
-	              LIBS SPIRV-Tools-comp SPIRV-Tools-opt ${SPIRV_TOOLS})
-    target_include_directories(spirv-markv PRIVATE ${spirv-tools_SOURCE_DIR}
-                                                   ${SPIRV_HEADER_INCLUDE_DIR})
-    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-markv)
-  endif(SPIRV_BUILD_COMPRESSION)
+  if(SPIRV_BUILD_FUZZER)
+    add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS})
+    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-fuzz)
+  endif(SPIRV_BUILD_FUZZER)
 
   if(ENABLE_SPIRV_TOOLS_INSTALL)
     install(TARGETS ${SPIRV_INSTALL_TARGETS}
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index 287ba51..acef5b4 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -21,6 +21,7 @@
 #include "tools/io.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(19, 80);
   printf(
       R"(%s - Create a SPIR-V binary module from SPIR-V assembly text
 
@@ -41,14 +42,13 @@
                   Numeric IDs in the binary will have the same values as in the
                   source. Non-numeric IDs are allocated by filling in the gaps,
                   starting with 1 and going up.
-  --target-env {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3}
-                  Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1,
-                  SPIR-V 1.2, or SPIR-V 1.3
+  --target-env    {%s}
+                  Use specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/cfg/cfg.cpp b/tools/cfg/cfg.cpp
index 9e2c448..411ef88 100644
--- a/tools/cfg/cfg.cpp
+++ b/tools/cfg/cfg.cpp
@@ -44,7 +44,7 @@
       argv0, argv0);
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_2;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/comp/markv.cpp b/tools/comp/markv.cpp
deleted file mode 100644
index 9a0a518..0000000
--- a/tools/comp/markv.cpp
+++ /dev/null
@@ -1,385 +0,0 @@
-// 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 <algorithm>
-#include <cassert>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <iostream>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "source/comp/markv.h"
-#include "source/spirv_target_env.h"
-#include "source/table.h"
-#include "spirv-tools/optimizer.hpp"
-#include "tools/comp/markv_model_factory.h"
-#include "tools/io.h"
-
-namespace {
-
-const auto kSpvEnv = SPV_ENV_UNIVERSAL_1_2;
-
-enum Task {
-  kNoTask = 0,
-  kEncode,
-  kDecode,
-  kTest,
-};
-
-struct ScopedContext {
-  ScopedContext(spv_target_env env) : context(spvContextCreate(env)) {}
-  ~ScopedContext() { spvContextDestroy(context); }
-  spv_context context;
-};
-
-void print_usage(char* argv0) {
-  printf(
-      R"(%s - Encodes or decodes a SPIR-V binary to or from a MARK-V binary.
-
-USAGE: %s [e|d|t] [options] [<filename>]
-
-The input binary is read from <filename>. If no file is specified,
-or if the filename is "-", then the binary is read from standard input.
-
-If no output is specified then the output is printed to stdout in a human
-readable format.
-
-WIP: MARK-V codec is in early stages of development. At the moment it only
-can encode and decode some SPIR-V files and only if exacly the same build of
-software is used (is doesn't write or handle version numbers yet).
-
-Tasks:
-  e               Encode SPIR-V to MARK-V.
-  d               Decode MARK-V to SPIR-V.
-  t               Test the codec by first encoding the given SPIR-V file to
-                  MARK-V, then decoding it back to SPIR-V and comparing results.
-
-Options:
-  -h, --help      Print this help.
-  --comments      Write codec comments to stderr.
-  --version       Display MARK-V codec version.
-  --validate      Validate SPIR-V while encoding or decoding.
-  --model=<model-name>
-                  Compression model, possible values:
-                  shader_lite - fast, poor compression ratio
-                  shader_mid - balanced
-                  shader_max - best compression ratio
-                  Default: shader_lite
-
-  -o <filename>   Set the output filename.
-                  Output goes to standard output if this option is
-                  not specified, or if the filename is "-".
-                  Not needed for 't' task (testing).
-)",
-      argv0, argv0);
-}
-
-void DiagnosticsMessageHandler(spv_message_level_t level, const char*,
-                               const spv_position_t& position,
-                               const char* message) {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: " << position.index << ": " << message << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cerr << "warning: " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cerr << "info: " << position.index << ": " << message << std::endl;
-      break;
-    default:
-      break;
-  }
-}
-
-}  // namespace
-
-int main(int argc, char** argv) {
-  const char* input_filename = nullptr;
-  const char* output_filename = nullptr;
-
-  Task task = kNoTask;
-
-  if (argc < 3) {
-    print_usage(argv[0]);
-    return 0;
-  }
-
-  const char* task_char = argv[1];
-  if (0 == strcmp("e", task_char)) {
-    task = kEncode;
-  } else if (0 == strcmp("d", task_char)) {
-    task = kDecode;
-  } else if (0 == strcmp("t", task_char)) {
-    task = kTest;
-  }
-
-  if (task == kNoTask) {
-    print_usage(argv[0]);
-    return 1;
-  }
-
-  bool want_comments = false;
-  bool validate_spirv_binary = false;
-
-  spvtools::comp::MarkvModelType model_type =
-      spvtools::comp::kMarkvModelUnknown;
-
-  for (int argi = 2; argi < argc; ++argi) {
-    if ('-' == argv[argi][0]) {
-      switch (argv[argi][1]) {
-        case 'h':
-          print_usage(argv[0]);
-          return 0;
-        case 'o': {
-          if (!output_filename && argi + 1 < argc &&
-              (task == kEncode || task == kDecode)) {
-            output_filename = argv[++argi];
-          } else {
-            print_usage(argv[0]);
-            return 1;
-          }
-        } break;
-        case '-': {
-          if (0 == strcmp(argv[argi], "--help")) {
-            print_usage(argv[0]);
-            return 0;
-          } else if (0 == strcmp(argv[argi], "--comments")) {
-            want_comments = true;
-          } else if (0 == strcmp(argv[argi], "--version")) {
-            fprintf(stderr, "error: Not implemented\n");
-            return 1;
-          } else if (0 == strcmp(argv[argi], "--validate")) {
-            validate_spirv_binary = true;
-          } else if (0 == strcmp(argv[argi], "--model=shader_lite")) {
-            if (model_type != spvtools::comp::kMarkvModelUnknown)
-              fprintf(stderr, "error: More than one model specified\n");
-            model_type = spvtools::comp::kMarkvModelShaderLite;
-          } else if (0 == strcmp(argv[argi], "--model=shader_mid")) {
-            if (model_type != spvtools::comp::kMarkvModelUnknown)
-              fprintf(stderr, "error: More than one model specified\n");
-            model_type = spvtools::comp::kMarkvModelShaderMid;
-          } else if (0 == strcmp(argv[argi], "--model=shader_max")) {
-            if (model_type != spvtools::comp::kMarkvModelUnknown)
-              fprintf(stderr, "error: More than one model specified\n");
-            model_type = spvtools::comp::kMarkvModelShaderMax;
-          } else {
-            print_usage(argv[0]);
-            return 1;
-          }
-        } break;
-        case '\0': {
-          // Setting a filename of "-" to indicate stdin.
-          if (!input_filename) {
-            input_filename = argv[argi];
-          } else {
-            fprintf(stderr, "error: More than one input file specified\n");
-            return 1;
-          }
-        } break;
-        default:
-          print_usage(argv[0]);
-          return 1;
-      }
-    } else {
-      if (!input_filename) {
-        input_filename = argv[argi];
-      } else {
-        fprintf(stderr, "error: More than one input file specified\n");
-        return 1;
-      }
-    }
-  }
-
-  if (model_type == spvtools::comp::kMarkvModelUnknown)
-    model_type = spvtools::comp::kMarkvModelShaderLite;
-
-  const auto no_comments = spvtools::comp::MarkvLogConsumer();
-  const auto output_to_stderr = [](const std::string& str) {
-    std::cerr << str;
-  };
-
-  ScopedContext ctx(kSpvEnv);
-
-  std::unique_ptr<spvtools::comp::MarkvModel> model =
-      spvtools::comp::CreateMarkvModel(model_type);
-
-  std::vector<uint32_t> spirv;
-  std::vector<uint8_t> markv;
-
-  spvtools::comp::MarkvCodecOptions options;
-  options.validate_spirv_binary = validate_spirv_binary;
-
-  if (task == kEncode) {
-    if (!ReadFile<uint32_t>(input_filename, "rb", &spirv)) return 1;
-    assert(!spirv.empty());
-
-    if (SPV_SUCCESS != spvtools::comp::SpirvToMarkv(
-                           ctx.context, spirv, options, *model,
-                           DiagnosticsMessageHandler,
-                           want_comments ? output_to_stderr : no_comments,
-                           spvtools::comp::MarkvDebugConsumer(), &markv)) {
-      std::cerr << "error: Failed to encode " << input_filename << " to MARK-V "
-                << std::endl;
-      return 1;
-    }
-
-    if (!WriteFile<uint8_t>(output_filename, "wb", markv.data(), markv.size()))
-      return 1;
-  } else if (task == kDecode) {
-    if (!ReadFile<uint8_t>(input_filename, "rb", &markv)) return 1;
-    assert(!markv.empty());
-
-    if (SPV_SUCCESS != spvtools::comp::MarkvToSpirv(
-                           ctx.context, markv, options, *model,
-                           DiagnosticsMessageHandler,
-                           want_comments ? output_to_stderr : no_comments,
-                           spvtools::comp::MarkvDebugConsumer(), &spirv)) {
-      std::cerr << "error: Failed to decode " << input_filename << " to SPIR-V "
-                << std::endl;
-      return 1;
-    }
-
-    if (!WriteFile<uint32_t>(output_filename, "wb", spirv.data(), spirv.size()))
-      return 1;
-  } else if (task == kTest) {
-    if (!ReadFile<uint32_t>(input_filename, "rb", &spirv)) return 1;
-    assert(!spirv.empty());
-
-    std::vector<uint32_t> spirv_before;
-    spvtools::Optimizer optimizer(kSpvEnv);
-    optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
-    if (!optimizer.Run(spirv.data(), spirv.size(), &spirv_before)) {
-      std::cerr << "error: Optimizer failure on: " << input_filename
-                << std::endl;
-    }
-
-    std::vector<std::string> encoder_instruction_bits;
-    std::vector<std::string> encoder_instruction_comments;
-    std::vector<std::vector<uint32_t>> encoder_instruction_words;
-    std::vector<std::string> decoder_instruction_bits;
-    std::vector<std::string> decoder_instruction_comments;
-    std::vector<std::vector<uint32_t>> decoder_instruction_words;
-
-    const auto encoder_debug_consumer = [&](const std::vector<uint32_t>& words,
-                                            const std::string& bits,
-                                            const std::string& comment) {
-      encoder_instruction_words.push_back(words);
-      encoder_instruction_bits.push_back(bits);
-      encoder_instruction_comments.push_back(comment);
-      return true;
-    };
-
-    if (SPV_SUCCESS != spvtools::comp::SpirvToMarkv(
-                           ctx.context, spirv_before, options, *model,
-                           DiagnosticsMessageHandler,
-                           want_comments ? output_to_stderr : no_comments,
-                           encoder_debug_consumer, &markv)) {
-      std::cerr << "error: Failed to encode " << input_filename << " to MARK-V "
-                << std::endl;
-      return 1;
-    }
-
-    const auto write_bug_report = [&]() {
-      for (size_t inst_index = 0; inst_index < decoder_instruction_words.size();
-           ++inst_index) {
-        std::cerr << "\nInstruction #" << inst_index << std::endl;
-        std::cerr << "\nEncoder words: ";
-        for (uint32_t word : encoder_instruction_words[inst_index])
-          std::cerr << word << " ";
-        std::cerr << "\nDecoder words: ";
-        for (uint32_t word : decoder_instruction_words[inst_index])
-          std::cerr << word << " ";
-        std::cerr << std::endl;
-
-        std::cerr << "\nEncoder bits: " << encoder_instruction_bits[inst_index];
-        std::cerr << "\nDecoder bits: " << decoder_instruction_bits[inst_index];
-        std::cerr << std::endl;
-
-        std::cerr << "\nEncoder comments:\n"
-                  << encoder_instruction_comments[inst_index];
-        std::cerr << "Decoder comments:\n"
-                  << decoder_instruction_comments[inst_index];
-        std::cerr << std::endl;
-      }
-    };
-
-    const auto decoder_debug_consumer = [&](const std::vector<uint32_t>& words,
-                                            const std::string& bits,
-                                            const std::string& comment) {
-      const size_t inst_index = decoder_instruction_words.size();
-      if (inst_index >= encoder_instruction_words.size()) {
-        write_bug_report();
-        std::cerr << "error: Decoder has more instructions than encoder: "
-                  << input_filename << std::endl;
-        return false;
-      }
-
-      decoder_instruction_words.push_back(words);
-      decoder_instruction_bits.push_back(bits);
-      decoder_instruction_comments.push_back(comment);
-
-      if (encoder_instruction_words[inst_index] !=
-          decoder_instruction_words[inst_index]) {
-        write_bug_report();
-        std::cerr << "error: Words of the last decoded instruction differ from "
-                     "reference: "
-                  << input_filename << std::endl;
-        return false;
-      }
-
-      if (encoder_instruction_bits[inst_index] !=
-          decoder_instruction_bits[inst_index]) {
-        write_bug_report();
-        std::cerr << "error: Bits of the last decoded instruction differ from "
-                     "reference: "
-                  << input_filename << std::endl;
-        return false;
-      }
-      return true;
-    };
-
-    std::vector<uint32_t> spirv_after;
-    const spv_result_t decoding_result = spvtools::comp::MarkvToSpirv(
-        ctx.context, markv, options, *model, DiagnosticsMessageHandler,
-        want_comments ? output_to_stderr : no_comments, decoder_debug_consumer,
-        &spirv_after);
-
-    if (decoding_result == SPV_REQUESTED_TERMINATION) {
-      std::cerr << "error: Decoding interrupted by the debugger: "
-                << input_filename << std::endl;
-      return 1;
-    }
-
-    if (decoding_result != SPV_SUCCESS) {
-      std::cerr << "error: Failed to decode encoded " << input_filename
-                << " back to SPIR-V " << std::endl;
-      return 1;
-    }
-
-    assert(spirv_before.size() == spirv_after.size());
-    assert(std::mismatch(std::next(spirv_before.begin(), 5), spirv_before.end(),
-                         std::next(spirv_after.begin(), 5)) ==
-           std::make_pair(spirv_before.end(), spirv_after.end()));
-  }
-
-  return 0;
-}
diff --git a/tools/comp/markv_model_factory.cpp b/tools/comp/markv_model_factory.cpp
deleted file mode 100644
index 863fcf5..0000000
--- a/tools/comp/markv_model_factory.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-// 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 "tools/comp/markv_model_factory.h"
-
-#include "source/util/make_unique.h"
-#include "tools/comp/markv_model_shader.h"
-
-namespace spvtools {
-namespace comp {
-
-std::unique_ptr<MarkvModel> CreateMarkvModel(MarkvModelType type) {
-  std::unique_ptr<MarkvModel> model;
-  switch (type) {
-    case kMarkvModelShaderLite: {
-      model = MakeUnique<MarkvModelShaderLite>();
-      break;
-    }
-    case kMarkvModelShaderMid: {
-      model = MakeUnique<MarkvModelShaderMid>();
-      break;
-    }
-    case kMarkvModelShaderMax: {
-      model = MakeUnique<MarkvModelShaderMax>();
-      break;
-    }
-    case kMarkvModelUnknown: {
-      assert(0 && "kMarkvModelUnknown supplied to CreateMarkvModel");
-      return model;
-    }
-  }
-
-  model->SetModelType(static_cast<uint32_t>(type));
-
-  return model;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/tools/comp/markv_model_factory.h b/tools/comp/markv_model_factory.h
deleted file mode 100644
index c13898b..0000000
--- a/tools/comp/markv_model_factory.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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.
-
-#ifndef TOOLS_COMP_MARKV_MODEL_FACTORY_H_
-#define TOOLS_COMP_MARKV_MODEL_FACTORY_H_
-
-#include <memory>
-
-#include "source/comp/markv_model.h"
-
-namespace spvtools {
-namespace comp {
-
-enum MarkvModelType {
-  kMarkvModelUnknown = 0,
-  kMarkvModelShaderLite,
-  kMarkvModelShaderMid,
-  kMarkvModelShaderMax,
-};
-
-std::unique_ptr<MarkvModel> CreateMarkvModel(MarkvModelType type);
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // TOOLS_COMP_MARKV_MODEL_FACTORY_H_
diff --git a/tools/comp/markv_model_shader.cpp b/tools/comp/markv_model_shader.cpp
deleted file mode 100644
index 8e296cd..0000000
--- a/tools/comp/markv_model_shader.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-// 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 "tools/comp/markv_model_shader.h"
-
-#include <algorithm>
-#include <map>
-#include <memory>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
-#include "source/util/make_unique.h"
-
-namespace spvtools {
-namespace comp {
-namespace {
-
-// Signals that the value is not in the coding scheme and a fallback method
-// needs to be used.
-const uint64_t kMarkvNoneOfTheAbove = MarkvModel::GetMarkvNoneOfTheAbove();
-
-inline uint32_t CombineOpcodeAndNumOperands(uint32_t opcode,
-                                            uint32_t num_operands) {
-  return opcode | (num_operands << 16);
-}
-
-#include "tools/comp/markv_model_shader_default_autogen.inc"
-
-}  // namespace
-
-MarkvModelShaderLite::MarkvModelShaderLite() {
-  const uint16_t kVersionNumber = 1;
-  SetModelVersion(kVersionNumber);
-
-  opcode_and_num_operands_huffman_codec_ =
-      MakeUnique<HuffmanCodec<uint64_t>>(GetOpcodeAndNumOperandsHist());
-
-  id_fallback_strategy_ = IdFallbackStrategy::kShortDescriptor;
-}
-
-MarkvModelShaderMid::MarkvModelShaderMid() {
-  const uint16_t kVersionNumber = 1;
-  SetModelVersion(kVersionNumber);
-
-  opcode_and_num_operands_huffman_codec_ =
-      MakeUnique<HuffmanCodec<uint64_t>>(GetOpcodeAndNumOperandsHist());
-  non_id_word_huffman_codecs_ = GetNonIdWordHuffmanCodecs();
-  id_descriptor_huffman_codecs_ = GetIdDescriptorHuffmanCodecs();
-  descriptors_with_coding_scheme_ = GetDescriptorsWithCodingScheme();
-  literal_string_huffman_codecs_ = GetLiteralStringHuffmanCodecs();
-
-  id_fallback_strategy_ = IdFallbackStrategy::kShortDescriptor;
-}
-
-MarkvModelShaderMax::MarkvModelShaderMax() {
-  const uint16_t kVersionNumber = 1;
-  SetModelVersion(kVersionNumber);
-
-  opcode_and_num_operands_huffman_codec_ =
-      MakeUnique<HuffmanCodec<uint64_t>>(GetOpcodeAndNumOperandsHist());
-  opcode_and_num_operands_markov_huffman_codecs_ =
-      GetOpcodeAndNumOperandsMarkovHuffmanCodecs();
-  non_id_word_huffman_codecs_ = GetNonIdWordHuffmanCodecs();
-  id_descriptor_huffman_codecs_ = GetIdDescriptorHuffmanCodecs();
-  descriptors_with_coding_scheme_ = GetDescriptorsWithCodingScheme();
-  literal_string_huffman_codecs_ = GetLiteralStringHuffmanCodecs();
-
-  id_fallback_strategy_ = IdFallbackStrategy::kRuleBased;
-}
-
-}  // namespace comp
-}  // namespace spvtools
diff --git a/tools/comp/markv_model_shader.h b/tools/comp/markv_model_shader.h
deleted file mode 100644
index 3a70457..0000000
--- a/tools/comp/markv_model_shader.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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.
-
-#ifndef TOOLS_COMP_MARKV_MODEL_SHADER_H_
-#define TOOLS_COMP_MARKV_MODEL_SHADER_H_
-
-#include "source/comp/markv_model.h"
-
-namespace spvtools {
-namespace comp {
-
-// MARK-V shader compression model, which only uses fast and lightweight
-// algorithms, which do not require training and are not heavily dependent on
-// SPIR-V grammar. Compression ratio is worse than by other models.
-class MarkvModelShaderLite : public MarkvModel {
- public:
-  MarkvModelShaderLite();
-};
-
-// MARK-V shader compression model with balanced compression ratio and runtime
-// performance.
-class MarkvModelShaderMid : public MarkvModel {
- public:
-  MarkvModelShaderMid();
-};
-
-// MARK-V shader compression model designed for maximum compression.
-class MarkvModelShaderMax : public MarkvModel {
- public:
-  MarkvModelShaderMax();
-};
-
-}  // namespace comp
-}  // namespace spvtools
-
-#endif  // TOOLS_COMP_MARKV_MODEL_SHADER_H_
diff --git a/tools/comp/markv_model_shader_default_autogen.inc b/tools/comp/markv_model_shader_default_autogen.inc
deleted file mode 100644
index 0093cf1..0000000
--- a/tools/comp/markv_model_shader_default_autogen.inc
+++ /dev/null
@@ -1,14519 +0,0 @@
-
-std::map<uint64_t, uint32_t> GetOpcodeAndNumOperandsHist() {
-  return std::map<uint64_t, uint32_t>({
-    { CombineOpcodeAndNumOperands(SpvOpExtInst, 7), 158282 },
-    { CombineOpcodeAndNumOperands(SpvOpDot, 4), 151035 },
-    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 6), 183292 },
-    { CombineOpcodeAndNumOperands(SpvOpImageSampleImplicitLod, 4), 126492 },
-    { CombineOpcodeAndNumOperands(SpvOpExecutionMode, 2), 13311 },
-    { CombineOpcodeAndNumOperands(SpvOpFNegate, 3), 29952 },
-    { CombineOpcodeAndNumOperands(SpvOpExtInst, 5), 106847 },
-    { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 7), 26350 },
-    { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 6), 28186 },
-    { CombineOpcodeAndNumOperands(SpvOpFDiv, 4), 41635 },
-    { CombineOpcodeAndNumOperands(SpvOpFMul, 4), 412786 },
-    { CombineOpcodeAndNumOperands(SpvOpFunction, 4), 62905 },
-    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 8), 118614 },
-    { CombineOpcodeAndNumOperands(SpvOpDecorate, 2), 100735 },
-    { CombineOpcodeAndNumOperands(SpvOpReturnValue, 1), 40852 },
-    { CombineOpcodeAndNumOperands(SpvOpVectorTimesScalar, 4), 157091 },
-    { CombineOpcodeAndNumOperands(SpvOpExtInst, 6), 122100 },
-    { CombineOpcodeAndNumOperands(SpvOpAccessChain, 5), 82930 },
-    { CombineOpcodeAndNumOperands(SpvOpFSub, 4), 161019 },
-    { CombineOpcodeAndNumOperands(SpvOpConstant, 3), 466014 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 5), 107126 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeImage, 8), 34775 },
-    { CombineOpcodeAndNumOperands(SpvOpImageSampleDrefExplicitLod, 7), 26146 },
-    { CombineOpcodeAndNumOperands(SpvOpMemoryModel, 2), 18879 },
-    { CombineOpcodeAndNumOperands(SpvOpDecorate, 3), 485251 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 4), 78011 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeFloat, 2), 18879 },
-    { CombineOpcodeAndNumOperands(SpvOpVectorTimesMatrix, 4), 15848 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeVector, 3), 69404 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 3), 19998 },
-    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 6), 40228 },
-    { CombineOpcodeAndNumOperands(SpvOpCapability, 1), 22510 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeArray, 3), 37585 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeInt, 3), 30454 },
-    { CombineOpcodeAndNumOperands(SpvOpFunctionCall, 4), 29021 },
-    { CombineOpcodeAndNumOperands(SpvOpFAdd, 4), 342237 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeMatrix, 3), 24449 },
-    { CombineOpcodeAndNumOperands(SpvOpLabel, 1), 129408 },
-    { CombineOpcodeAndNumOperands(SpvOpTypePointer, 3), 246535 },
-    { CombineOpcodeAndNumOperands(SpvOpAccessChain, 4), 503456 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 2), 19779 },
-    { CombineOpcodeAndNumOperands(SpvOpBranchConditional, 3), 24139 },
-    { CombineOpcodeAndNumOperands(SpvOpVariable, 3), 697946 },
-    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 5), 55769 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeVoid, 1), 18879 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 6), 145508 },
-    { CombineOpcodeAndNumOperands(SpvOpFunctionParameter, 2), 85583 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeSampledImage, 2), 34775 },
-    { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 4), 66362 },
-    { CombineOpcodeAndNumOperands(SpvOpLoad, 3), 1272902 },
-    { CombineOpcodeAndNumOperands(SpvOpReturn, 0), 22122 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 4), 861008 },
-    { CombineOpcodeAndNumOperands(SpvOpFunctionEnd, 0), 62905 },
-    { CombineOpcodeAndNumOperands(SpvOpExtInstImport, 2), 18879 },
-    { CombineOpcodeAndNumOperands(SpvOpSelectionMerge, 2), 22009 },
-    { CombineOpcodeAndNumOperands(SpvOpBranch, 1), 38275 },
-    { CombineOpcodeAndNumOperands(SpvOpTypeBool, 1), 12208 },
-    { CombineOpcodeAndNumOperands(SpvOpSampledImage, 4), 95518 },
-    { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 3), 94887 },
-    { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 4), 1942215 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 5), 205266 },
-    { CombineOpcodeAndNumOperands(SpvOpUndef, 2), 22157 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 5), 142749 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 6), 24420 },
-    { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 6), 16896 },
-    { CombineOpcodeAndNumOperands(SpvOpStore, 2), 604982 },
-    { CombineOpcodeAndNumOperands(SpvOpIAdd, 4), 14471 },
-    { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 7), 269658 },
-    { kMarkvNoneOfTheAbove, 399895 },
-  });
-}
-
-std::map<uint32_t, std::unique_ptr<HuffmanCodec<uint64_t>>>
-GetOpcodeAndNumOperandsMarkovHuffmanCodecs() {
-  std::map<uint32_t, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {262209, 0, 0},
-      {262221, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262273, 0, 0},
-      {262277, 0, 0},
-      {262286, 0, 0},
-      {327745, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393295, 0, 0},
-      {393304, 0, 0},
-      {458831, 0, 0},
-      {458840, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 11, 8},
-      {0, 12, 19},
-      {0, 18, 20},
-      {0, 5, 21},
-      {0, 15, 7},
-      {0, 10, 1},
-      {0, 23, 22},
-      {0, 14, 24},
-      {0, 6, 4},
-      {0, 2, 17},
-      {0, 13, 25},
-      {0, 9, 26},
-      {0, 28, 27},
-      {0, 3, 29},
-      {0, 30, 16},
-      {0, 32, 31},
-      {0, 34, 33},
-    }));
-
-    codecs.emplace(SpvOpImageSampleExplicitLod, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393303, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 5},
-      {0, 29, 17},
-      {0, 1, 30},
-      {0, 10, 20},
-      {0, 32, 31},
-      {0, 33, 2},
-      {0, 34, 23},
-      {0, 8, 35},
-      {0, 6, 36},
-      {0, 19, 22},
-      {0, 28, 25},
-      {0, 38, 37},
-      {0, 13, 39},
-      {0, 40, 24},
-      {0, 27, 21},
-      {0, 26, 41},
-      {0, 42, 12},
-      {0, 15, 43},
-      {0, 44, 18},
-      {0, 45, 3},
-      {0, 11, 7},
-      {0, 16, 46},
-      {0, 47, 9},
-      {0, 4, 48},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-    }));
-
-    codecs.emplace(SpvOpFDiv, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(19, {
-      {0, 0, 0},
-      {196669, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262286, 0, 0},
-      {393295, 0, 0},
-      {393304, 0, 0},
-      {458840, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 10},
-      {0, 11, 3},
-      {0, 2, 9},
-      {0, 4, 1},
-      {0, 5, 6},
-      {0, 13, 12},
-      {0, 15, 14},
-      {0, 16, 7},
-      {0, 18, 17},
-    }));
-
-    codecs.emplace(SpvOpSampledImage, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262285, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393297, 0, 0},
-      {393298, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 10},
-      {0, 30, 35},
-      {0, 1, 36},
-      {0, 11, 37},
-      {0, 38, 6},
-      {0, 16, 39},
-      {0, 15, 40},
-      {0, 25, 2},
-      {0, 41, 20},
-      {0, 26, 19},
-      {0, 42, 29},
-      {0, 28, 22},
-      {0, 23, 34},
-      {0, 44, 43},
-      {0, 17, 45},
-      {0, 24, 27},
-      {0, 18, 33},
-      {0, 47, 46},
-      {0, 8, 48},
-      {0, 50, 49},
-      {0, 32, 51},
-      {0, 31, 52},
-      {0, 53, 21},
-      {0, 54, 13},
-      {0, 3, 55},
-      {0, 7, 14},
-      {0, 57, 56},
-      {0, 58, 5},
-      {0, 59, 9},
-      {0, 61, 60},
-      {0, 63, 62},
-      {0, 64, 12},
-      {0, 66, 65},
-    }));
-
-    codecs.emplace(SpvOpFMul, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {262328, 0, 0},
-      {262334, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393297, 0, 0},
-      {393303, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {458840, 0, 0},
-      {524345, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 38, 33},
-      {0, 18, 41},
-      {0, 42, 23},
-      {0, 43, 6},
-      {0, 34, 44},
-      {0, 1, 45},
-      {0, 31, 14},
-      {0, 47, 46},
-      {0, 48, 2},
-      {0, 12, 21},
-      {0, 49, 30},
-      {0, 37, 50},
-      {0, 51, 20},
-      {0, 5, 24},
-      {0, 40, 16},
-      {0, 29, 13},
-      {0, 26, 52},
-      {0, 53, 17},
-      {0, 36, 54},
-      {0, 55, 28},
-      {0, 57, 56},
-      {0, 19, 25},
-      {0, 39, 8},
-      {0, 32, 58},
-      {0, 59, 27},
-      {0, 22, 10},
-      {0, 35, 60},
-      {0, 62, 61},
-      {0, 63, 7},
-      {0, 65, 64},
-      {0, 4, 66},
-      {0, 68, 67},
-      {0, 11, 3},
-      {0, 15, 69},
-      {0, 9, 70},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-    }));
-
-    codecs.emplace(SpvOpFAdd, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {65562, 0, 0},
-      {131073, 0, 0},
-      {131094, 0, 0},
-      {131105, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262177, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {327713, 0, 0},
-      {327724, 0, 0},
-      {393249, 0, 0},
-      {393260, 0, 0},
-      {458785, 0, 0},
-      {524313, 0, 0},
-      {524321, 0, 0},
-      {589857, 0, 0},
-      {655393, 0, 0},
-      {720929, 0, 0},
-      {852001, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 26, 24},
-      {0, 29, 27},
-      {0, 4, 30},
-      {0, 21, 9},
-      {0, 31, 20},
-      {0, 33, 32},
-      {0, 34, 3},
-      {0, 8, 35},
-      {0, 36, 5},
-      {0, 23, 16},
-      {0, 38, 37},
-      {0, 25, 2},
-      {0, 39, 1},
-      {0, 17, 40},
-      {0, 41, 15},
-      {0, 18, 42},
-      {0, 43, 6},
-      {0, 44, 14},
-      {0, 28, 19},
-      {0, 7, 45},
-      {0, 46, 22},
-      {0, 48, 47},
-      {0, 49, 11},
-      {0, 51, 50},
-      {0, 12, 10},
-      {0, 53, 52},
-      {0, 13, 54},
-    }));
-
-    codecs.emplace(SpvOpTypePointer, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {262328, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 9, 23},
-      {0, 1, 30},
-      {0, 5, 31},
-      {0, 32, 28},
-      {0, 33, 25},
-      {0, 34, 29},
-      {0, 18, 24},
-      {0, 27, 16},
-      {0, 7, 13},
-      {0, 14, 35},
-      {0, 20, 10},
-      {0, 36, 21},
-      {0, 2, 37},
-      {0, 38, 3},
-      {0, 39, 22},
-      {0, 40, 19},
-      {0, 41, 11},
-      {0, 6, 4},
-      {0, 12, 42},
-      {0, 43, 8},
-      {0, 15, 26},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 48, 17},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(SpvOpFSub, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {131134, 0, 0},
-      {196719, 0, 0},
-      {262209, 0, 0},
-      {262276, 0, 0},
-      {327745, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 4},
-      {0, 2, 8},
-      {0, 1, 9},
-      {0, 5, 10},
-      {0, 3, 6},
-      {0, 12, 11},
-    }));
-
-    codecs.emplace(SpvOpIAdd, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196669, 0, 0},
-      {196732, 0, 0},
-      {196735, 0, 0},
-      {262209, 0, 0},
-      {262221, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262231, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {262328, 0, 0},
-      {262334, 0, 0},
-      {262340, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393297, 0, 0},
-      {393298, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {458840, 0, 0},
-      {458842, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 25, 2},
-      {0, 31, 43},
-      {0, 4, 44},
-      {0, 26, 45},
-      {0, 39, 46},
-      {0, 34, 36},
-      {0, 19, 47},
-      {0, 6, 48},
-      {0, 35, 9},
-      {0, 12, 29},
-      {0, 21, 49},
-      {0, 22, 13},
-      {0, 17, 50},
-      {0, 23, 51},
-      {0, 52, 7},
-      {0, 37, 1},
-      {0, 53, 3},
-      {0, 54, 24},
-      {0, 56, 55},
-      {0, 32, 57},
-      {0, 59, 58},
-      {0, 42, 10},
-      {0, 60, 8},
-      {0, 5, 41},
-      {0, 61, 20},
-      {0, 62, 38},
-      {0, 64, 63},
-      {0, 40, 65},
-      {0, 66, 18},
-      {0, 15, 28},
-      {0, 14, 67},
-      {0, 68, 30},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 73, 27},
-      {0, 16, 74},
-      {0, 75, 33},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 82, 11},
-    }));
-
-    codecs.emplace(SpvOpCompositeExtract, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(29, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {262209, 0, 0},
-      {262225, 0, 0},
-      {262273, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393295, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 10, 6},
-      {0, 16, 13},
-      {0, 7, 17},
-      {0, 15, 18},
-      {0, 19, 12},
-      {0, 20, 14},
-      {0, 1, 4},
-      {0, 22, 21},
-      {0, 11, 8},
-      {0, 2, 5},
-      {0, 9, 23},
-      {0, 3, 24},
-      {0, 26, 25},
-      {0, 28, 27},
-    }));
-
-    codecs.emplace(SpvOpVectorTimesMatrix, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {65784, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpBranch, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {262198, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpFunctionEnd, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {65784, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpBranchConditional, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(53, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196665, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {393228, 0, 0},
-      {393295, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 25, 16},
-      {0, 21, 28},
-      {0, 18, 23},
-      {0, 4, 29},
-      {0, 10, 5},
-      {0, 1, 30},
-      {0, 32, 31},
-      {0, 22, 33},
-      {0, 34, 8},
-      {0, 35, 15},
-      {0, 13, 36},
-      {0, 26, 17},
-      {0, 38, 37},
-      {0, 39, 11},
-      {0, 40, 14},
-      {0, 12, 27},
-      {0, 19, 41},
-      {0, 24, 42},
-      {0, 44, 43},
-      {0, 45, 7},
-      {0, 20, 46},
-      {0, 9, 47},
-      {0, 48, 2},
-      {0, 50, 49},
-      {0, 6, 3},
-      {0, 52, 51},
-    }));
-
-    codecs.emplace(SpvOpFunctionCall, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(71, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {65562, 0, 0},
-      {131073, 0, 0},
-      {131094, 0, 0},
-      {131099, 0, 0},
-      {131134, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196665, 0, 0},
-      {196667, 0, 0},
-      {196669, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262225, 0, 0},
-      {262275, 0, 0},
-      {262280, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327724, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {393228, 0, 0},
-      {393260, 0, 0},
-      {393273, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458831, 0, 0},
-      {524313, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 22, 4},
-      {0, 32, 23},
-      {0, 37, 30},
-      {0, 21, 38},
-      {0, 39, 31},
-      {0, 41, 40},
-      {0, 13, 42},
-      {0, 43, 26},
-      {0, 10, 44},
-      {0, 28, 45},
-      {0, 35, 18},
-      {0, 20, 46},
-      {0, 33, 47},
-      {0, 24, 48},
-      {0, 6, 49},
-      {0, 3, 50},
-      {0, 16, 51},
-      {0, 27, 52},
-      {0, 53, 1},
-      {0, 9, 17},
-      {0, 29, 54},
-      {0, 19, 2},
-      {0, 8, 36},
-      {0, 55, 34},
-      {0, 25, 56},
-      {0, 7, 57},
-      {0, 5, 58},
-      {0, 60, 59},
-      {0, 61, 15},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 66, 11},
-      {0, 12, 67},
-      {0, 69, 68},
-      {0, 14, 70},
-    }));
-
-    codecs.emplace(SpvOpVariable, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(SpvOpAccessChain, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(73, {
-      {0, 0, 0},
-      {252, 0, 0},
-      {253, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131073, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196665, 0, 0},
-      {196667, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {196854, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262221, 0, 0},
-      {262225, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262276, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {262321, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393298, 0, 0},
-      {393461, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 28, 5},
-      {0, 30, 8},
-      {0, 13, 38},
-      {0, 40, 39},
-      {0, 41, 26},
-      {0, 42, 19},
-      {0, 43, 29},
-      {0, 23, 44},
-      {0, 36, 32},
-      {0, 45, 22},
-      {0, 2, 46},
-      {0, 21, 20},
-      {0, 48, 47},
-      {0, 33, 49},
-      {0, 4, 50},
-      {0, 51, 24},
-      {0, 18, 11},
-      {0, 52, 12},
-      {0, 25, 15},
-      {0, 53, 17},
-      {0, 37, 54},
-      {0, 55, 35},
-      {0, 7, 27},
-      {0, 57, 56},
-      {0, 58, 31},
-      {0, 6, 59},
-      {0, 1, 60},
-      {0, 62, 61},
-      {0, 63, 14},
-      {0, 3, 16},
-      {0, 34, 64},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 10, 9},
-      {0, 72, 71},
-    }));
-
-    codecs.emplace(SpvOpLabel, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {56, 0, 0},
-      {65784, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(SpvOpReturn, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {65784, 0, 0},
-      {131127, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(SpvOpFunction, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262177, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {327713, 0, 0},
-      {393260, 0, 0},
-      {524313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 1},
-      {0, 13, 5},
-      {0, 18, 17},
-      {0, 7, 19},
-      {0, 9, 20},
-      {0, 16, 21},
-      {0, 15, 10},
-      {0, 22, 4},
-      {0, 24, 23},
-      {0, 25, 14},
-      {0, 8, 11},
-      {0, 2, 26},
-      {0, 28, 27},
-      {0, 3, 6},
-      {0, 30, 29},
-    }));
-
-    codecs.emplace(SpvOpTypeVector, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {65784, 0, 0},
-      {131127, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 4, 1},
-    }));
-
-    codecs.emplace(SpvOpFunctionParameter, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {56, 0, 0},
-      {65784, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(SpvOpReturnValue, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {131105, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpTypeVoid, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
-      {0, 0, 0},
-      {253, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196665, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458764, 0, 0},
-      {458809, 0, 0},
-      {458831, 0, 0},
-      {524345, 0, 0},
-      {524367, 0, 0},
-      {589881, 0, 0},
-      {655417, 0, 0},
-      {720953, 0, 0},
-      {786489, 0, 0},
-      {852025, 0, 0},
-      {917561, 0, 0},
-      {983097, 0, 0},
-      {1114169, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 40, 32},
-      {0, 46, 29},
-      {0, 38, 27},
-      {0, 20, 47},
-      {0, 49, 48},
-      {0, 50, 44},
-      {0, 51, 43},
-      {0, 14, 5},
-      {0, 42, 52},
-      {0, 13, 19},
-      {0, 3, 26},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 57, 6},
-      {0, 39, 37},
-      {0, 15, 58},
-      {0, 18, 31},
-      {0, 59, 21},
-      {0, 60, 17},
-      {0, 61, 41},
-      {0, 62, 24},
-      {0, 34, 63},
-      {0, 35, 64},
-      {0, 65, 8},
-      {0, 66, 36},
-      {0, 67, 30},
-      {0, 16, 11},
-      {0, 69, 68},
-      {0, 70, 28},
-      {0, 22, 71},
-      {0, 33, 72},
-      {0, 45, 73},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 78, 12},
-      {0, 1, 2},
-      {0, 9, 79},
-      {0, 25, 80},
-      {0, 23, 81},
-      {0, 4, 82},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 7, 10},
-      {0, 88, 87},
-    }));
-
-    codecs.emplace(SpvOpStore, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {131075, 0, 0},
-      {131088, 0, 0},
-      {131143, 0, 0},
-      {196624, 0, 0},
-      {196679, 0, 0},
-      {262216, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 8},
-      {0, 7, 9},
-      {0, 6, 10},
-      {0, 5, 11},
-      {0, 2, 12},
-    }));
-
-    codecs.emplace(SpvOpEntryPoint, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(97, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196665, 0, 0},
-      {196669, 0, 0},
-      {196732, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262276, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {262326, 0, 0},
-      {262328, 0, 0},
-      {262330, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393297, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458809, 0, 0},
-      {458817, 0, 0},
-      {458831, 0, 0},
-      {458840, 0, 0},
-      {524345, 0, 0},
-      {524367, 0, 0},
-      {589881, 0, 0},
-      {720953, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 42, 47},
-      {0, 48, 50},
-      {0, 45, 51},
-      {0, 34, 52},
-      {0, 53, 41},
-      {0, 1, 54},
-      {0, 55, 5},
-      {0, 15, 4},
-      {0, 56, 35},
-      {0, 26, 24},
-      {0, 18, 28},
-      {0, 57, 38},
-      {0, 59, 58},
-      {0, 60, 25},
-      {0, 20, 9},
-      {0, 7, 61},
-      {0, 62, 22},
-      {0, 11, 31},
-      {0, 63, 8},
-      {0, 64, 40},
-      {0, 66, 65},
-      {0, 27, 44},
-      {0, 29, 67},
-      {0, 68, 39},
-      {0, 69, 2},
-      {0, 37, 49},
-      {0, 71, 70},
-      {0, 30, 72},
-      {0, 73, 17},
-      {0, 33, 74},
-      {0, 23, 14},
-      {0, 32, 75},
-      {0, 21, 76},
-      {0, 77, 16},
-      {0, 46, 78},
-      {0, 13, 79},
-      {0, 80, 12},
-      {0, 19, 81},
-      {0, 43, 36},
-      {0, 83, 82},
-      {0, 10, 84},
-      {0, 85, 3},
-      {0, 6, 86},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-    }));
-
-    codecs.emplace(SpvOpLoad, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
-      {0, 0, 0},
-      {262159, 0, 0},
-      {327695, 0, 0},
-      {393231, 0, 0},
-      {458767, 0, 0},
-      {524303, 0, 0},
-      {589839, 0, 0},
-      {655375, 0, 0},
-      {720911, 0, 0},
-      {786447, 0, 0},
-      {851983, 0, 0},
-      {917519, 0, 0},
-      {983055, 0, 0},
-      {1048591, 0, 0},
-      {1114127, 0, 0},
-      {1179663, 0, 0},
-      {1245199, 0, 0},
-      {1310735, 0, 0},
-      {1376271, 0, 0},
-      {1441807, 0, 0},
-      {1507343, 0, 0},
-      {1572879, 0, 0},
-      {1638415, 0, 0},
-      {1703951, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 23},
-      {0, 22, 25},
-      {0, 21, 26},
-      {0, 6, 20},
-      {0, 19, 27},
-      {0, 29, 28},
-      {0, 24, 18},
-      {0, 30, 13},
-      {0, 31, 14},
-      {0, 32, 7},
-      {0, 17, 15},
-      {0, 33, 2},
-      {0, 34, 8},
-      {0, 16, 12},
-      {0, 35, 3},
-      {0, 36, 5},
-      {0, 9, 37},
-      {0, 39, 38},
-      {0, 11, 40},
-      {0, 4, 10},
-      {0, 42, 41},
-      {0, 44, 43},
-      {0, 46, 45},
-    }));
-
-    codecs.emplace(SpvOpMemoryModel, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {196631, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 4, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(SpvOpTypeFloat, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(69, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262289, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {327849, 0, 0},
-      {393228, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458809, 0, 0},
-      {458831, 0, 0},
-      {524345, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 33, 10},
-      {0, 31, 36},
-      {0, 26, 37},
-      {0, 5, 38},
-      {0, 20, 39},
-      {0, 22, 40},
-      {0, 24, 25},
-      {0, 15, 41},
-      {0, 9, 17},
-      {0, 1, 42},
-      {0, 4, 43},
-      {0, 35, 44},
-      {0, 34, 45},
-      {0, 19, 46},
-      {0, 7, 29},
-      {0, 16, 47},
-      {0, 48, 32},
-      {0, 49, 27},
-      {0, 11, 14},
-      {0, 18, 28},
-      {0, 23, 50},
-      {0, 51, 12},
-      {0, 52, 21},
-      {0, 6, 53},
-      {0, 55, 54},
-      {0, 57, 56},
-      {0, 3, 58},
-      {0, 13, 59},
-      {0, 60, 8},
-      {0, 30, 61},
-      {0, 62, 2},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 68, 67},
-    }));
-
-    codecs.emplace(SpvOpCompositeConstruct, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {131094, 0, 0},
-      {131105, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {262177, 0, 0},
-      {327713, 0, 0},
-      {393249, 0, 0},
-      {458785, 0, 0},
-      {524313, 0, 0},
-      {524321, 0, 0},
-      {589857, 0, 0},
-      {655393, 0, 0},
-      {786465, 0, 0},
-      {917537, 0, 0},
-      {1048609, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 19, 18},
-      {0, 21, 15},
-      {0, 1, 22},
-      {0, 16, 23},
-      {0, 14, 24},
-      {0, 20, 25},
-      {0, 13, 17},
-      {0, 3, 26},
-      {0, 6, 11},
-      {0, 27, 12},
-      {0, 4, 28},
-      {0, 29, 10},
-      {0, 9, 30},
-      {0, 7, 31},
-      {0, 33, 32},
-      {0, 34, 5},
-      {0, 8, 35},
-      {0, 2, 36},
-      {0, 38, 37},
-    }));
-
-    codecs.emplace(SpvOpTypeFunction, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {131086, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpExtInstImport, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {131099, 0, 0},
-      {196640, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(SpvOpTypeImage, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {131143, 0, 0},
-      {196679, 0, 0},
-      {196680, 0, 0},
-      {262216, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 2},
-      {0, 3, 6},
-      {0, 7, 1},
-      {0, 4, 8},
-    }));
-
-    codecs.emplace(SpvOpMemberDecorate, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {65553, 0, 0},
-      {131083, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(SpvOpCapability, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
-      {0, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {327713, 0, 0},
-      {458785, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 8},
-      {0, 1, 10},
-      {0, 6, 11},
-      {0, 9, 12},
-      {0, 4, 13},
-      {0, 3, 14},
-      {0, 15, 2},
-      {0, 5, 16},
-    }));
-
-    codecs.emplace(SpvOpTypeInt, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(29, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {131073, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {327724, 0, 0},
-      {393260, 0, 0},
-      {524313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 6},
-      {0, 16, 3},
-      {0, 11, 17},
-      {0, 5, 18},
-      {0, 15, 19},
-      {0, 13, 20},
-      {0, 1, 4},
-      {0, 12, 21},
-      {0, 7, 22},
-      {0, 14, 23},
-      {0, 24, 10},
-      {0, 25, 9},
-      {0, 27, 26},
-      {0, 8, 28},
-    }));
-
-    codecs.emplace(SpvOpConstantComposite, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {196631, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {327724, 0, 0},
-      {393260, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 7},
-      {0, 1, 9},
-      {0, 10, 8},
-      {0, 2, 11},
-      {0, 5, 12},
-      {0, 13, 4},
-      {0, 3, 14},
-    }));
-
-    codecs.emplace(SpvOpTypeSampledImage, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(21, {
-      {0, 0, 0},
-      {131073, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262198, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 11, 12},
-      {0, 8, 13},
-      {0, 7, 14},
-      {0, 4, 10},
-      {0, 9, 2},
-      {0, 16, 15},
-      {0, 1, 17},
-      {0, 19, 18},
-      {0, 6, 20},
-    }));
-
-    codecs.emplace(SpvOpTypeStruct, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 20, 12},
-      {0, 26, 24},
-      {0, 21, 27},
-      {0, 28, 16},
-      {0, 10, 8},
-      {0, 30, 29},
-      {0, 31, 17},
-      {0, 32, 13},
-      {0, 25, 6},
-      {0, 1, 33},
-      {0, 14, 11},
-      {0, 3, 34},
-      {0, 18, 35},
-      {0, 37, 36},
-      {0, 23, 5},
-      {0, 38, 2},
-      {0, 39, 7},
-      {0, 4, 9},
-      {0, 40, 19},
-      {0, 42, 41},
-      {0, 43, 22},
-      {0, 45, 44},
-      {0, 46, 15},
-      {0, 48, 47},
-    }));
-
-    codecs.emplace(SpvOpFNegate, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {65555, 0, 0},
-      {131143, 0, 0},
-      {196679, 0, 0},
-      {196680, 0, 0},
-      {262216, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 6},
-      {0, 1, 2},
-      {0, 8, 7},
-      {0, 5, 9},
-      {0, 3, 10},
-    }));
-
-    codecs.emplace(SpvOpDecorate, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
-      {0, 0, 0},
-      {65562, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262177, 0, 0},
-      {262198, 0, 0},
-      {327713, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 11},
-      {0, 9, 14},
-      {0, 10, 15},
-      {0, 13, 16},
-      {0, 4, 17},
-      {0, 2, 1},
-      {0, 18, 7},
-      {0, 20, 19},
-      {0, 21, 3},
-      {0, 22, 6},
-      {0, 5, 8},
-      {0, 24, 23},
-    }));
-
-    codecs.emplace(SpvOpTypeMatrix, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
-      {0, 0, 0},
-      {65556, 0, 0},
-      {131073, 0, 0},
-      {131094, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {327724, 0, 0},
-      {393260, 0, 0},
-      {524313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 2},
-      {0, 17, 3},
-      {0, 5, 18},
-      {0, 1, 19},
-      {0, 16, 4},
-      {0, 21, 20},
-      {0, 6, 15},
-      {0, 7, 22},
-      {0, 24, 23},
-      {0, 13, 14},
-      {0, 25, 8},
-      {0, 26, 11},
-      {0, 27, 10},
-      {0, 29, 28},
-      {0, 30, 9},
-    }));
-
-    codecs.emplace(SpvOpConstant, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
-      {0, 0, 0},
-      {131113, 0, 0},
-      {196629, 0, 0},
-      {196631, 0, 0},
-      {196632, 0, 0},
-      {196640, 0, 0},
-      {196641, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {262188, 0, 0},
-      {262198, 0, 0},
-      {327713, 0, 0},
-      {327724, 0, 0},
-      {393249, 0, 0},
-      {393260, 0, 0},
-      {524313, 0, 0},
-      {524321, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 4},
-      {0, 13, 11},
-      {0, 16, 15},
-      {0, 18, 10},
-      {0, 20, 19},
-      {0, 21, 2},
-      {0, 23, 22},
-      {0, 8, 24},
-      {0, 9, 25},
-      {0, 17, 26},
-      {0, 14, 27},
-      {0, 12, 28},
-      {0, 1, 3},
-      {0, 5, 29},
-      {0, 30, 7},
-      {0, 32, 31},
-    }));
-
-    codecs.emplace(SpvOpTypeBool, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {196636, 0, 0},
-      {196640, 0, 0},
-      {196651, 0, 0},
-      {196667, 0, 0},
-      {524313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 3, 7},
-      {0, 2, 8},
-      {0, 6, 9},
-      {0, 1, 10},
-    }));
-
-    codecs.emplace(SpvOpTypeArray, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {262334, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 27},
-      {0, 11, 28},
-      {0, 35, 21},
-      {0, 36, 1},
-      {0, 4, 37},
-      {0, 39, 38},
-      {0, 40, 30},
-      {0, 41, 12},
-      {0, 19, 42},
-      {0, 13, 43},
-      {0, 16, 44},
-      {0, 45, 22},
-      {0, 34, 18},
-      {0, 29, 24},
-      {0, 46, 25},
-      {0, 6, 2},
-      {0, 9, 31},
-      {0, 17, 47},
-      {0, 49, 48},
-      {0, 50, 33},
-      {0, 51, 26},
-      {0, 20, 52},
-      {0, 32, 53},
-      {0, 3, 54},
-      {0, 15, 14},
-      {0, 23, 55},
-      {0, 8, 56},
-      {0, 58, 57},
-      {0, 10, 59},
-      {0, 5, 60},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-    }));
-
-    codecs.emplace(SpvOpExtInst, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196665, 0, 0},
-      {196669, 0, 0},
-      {196718, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393303, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 18, 6},
-      {0, 30, 22},
-      {0, 31, 25},
-      {0, 10, 32},
-      {0, 21, 33},
-      {0, 3, 34},
-      {0, 35, 5},
-      {0, 23, 36},
-      {0, 14, 17},
-      {0, 37, 26},
-      {0, 1, 38},
-      {0, 29, 39},
-      {0, 13, 40},
-      {0, 41, 19},
-      {0, 28, 20},
-      {0, 16, 42},
-      {0, 27, 43},
-      {0, 8, 24},
-      {0, 7, 44},
-      {0, 9, 45},
-      {0, 15, 46},
-      {0, 12, 47},
-      {0, 48, 2},
-      {0, 4, 49},
-      {0, 51, 50},
-      {0, 11, 52},
-      {0, 54, 53},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(SpvOpVectorTimesScalar, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262231, 0, 0},
-      {262272, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327737, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393273, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393303, 0, 0},
-      {393304, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 26, 29},
-      {0, 20, 35},
-      {0, 12, 36},
-      {0, 6, 37},
-      {0, 38, 28},
-      {0, 30, 5},
-      {0, 8, 39},
-      {0, 2, 40},
-      {0, 41, 21},
-      {0, 1, 10},
-      {0, 43, 42},
-      {0, 23, 16},
-      {0, 44, 33},
-      {0, 34, 31},
-      {0, 14, 45},
-      {0, 19, 46},
-      {0, 25, 47},
-      {0, 49, 48},
-      {0, 27, 22},
-      {0, 7, 50},
-      {0, 17, 32},
-      {0, 18, 51},
-      {0, 24, 52},
-      {0, 54, 53},
-      {0, 55, 9},
-      {0, 56, 11},
-      {0, 57, 4},
-      {0, 15, 58},
-      {0, 59, 13},
-      {0, 60, 3},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-    }));
-
-    codecs.emplace(SpvOpVectorShuffle, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {262201, 0, 0},
-      {262209, 0, 0},
-      {262225, 0, 0},
-      {262231, 0, 0},
-      {262273, 0, 0},
-      {262277, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327745, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {458831, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 13, 12},
-      {0, 1, 18},
-      {0, 19, 11},
-      {0, 9, 20},
-      {0, 10, 21},
-      {0, 22, 15},
-      {0, 23, 8},
-      {0, 4, 24},
-      {0, 25, 7},
-      {0, 17, 26},
-      {0, 5, 27},
-      {0, 14, 3},
-      {0, 29, 28},
-      {0, 30, 2},
-      {0, 6, 31},
-      {0, 32, 16},
-    }));
-
-    codecs.emplace(SpvOpImageSampleImplicitLod, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(55, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {65790, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {196817, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262292, 0, 0},
-      {327692, 0, 0},
-      {327745, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393281, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393298, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 2},
-      {0, 22, 29},
-      {0, 30, 1},
-      {0, 6, 31},
-      {0, 9, 32},
-      {0, 28, 3},
-      {0, 27, 33},
-      {0, 20, 16},
-      {0, 34, 8},
-      {0, 10, 35},
-      {0, 4, 36},
-      {0, 24, 23},
-      {0, 21, 13},
-      {0, 7, 37},
-      {0, 38, 14},
-      {0, 25, 39},
-      {0, 17, 11},
-      {0, 12, 19},
-      {0, 41, 40},
-      {0, 42, 18},
-      {0, 15, 43},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 26, 48},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-    }));
-
-    codecs.emplace(SpvOpDot, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {131075, 0, 0},
-      {131088, 0, 0},
-      {196624, 0, 0},
-      {196679, 0, 0},
-      {262216, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 3},
-      {0, 2, 7},
-      {0, 1, 8},
-      {0, 6, 9},
-      {0, 4, 10},
-    }));
-
-    codecs.emplace(SpvOpExecutionMode, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {196858, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(SpvOpSelectionMerge, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
-      {0, 0, 0},
-      {131134, 0, 0},
-      {196669, 0, 0},
-      {262209, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262277, 0, 0},
-      {327745, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 12},
-      {0, 7, 13},
-      {0, 5, 1},
-      {0, 4, 10},
-      {0, 14, 6},
-      {0, 16, 15},
-      {0, 17, 11},
-      {0, 3, 8},
-      {0, 19, 18},
-      {0, 9, 20},
-      {0, 22, 21},
-    }));
-
-    codecs.emplace(SpvOpImageSampleDrefExplicitLod, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {65790, 0, 0},
-      {131073, 0, 0},
-      {262198, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 1},
-      {0, 3, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(SpvOpUndef, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
-      {0, 0, 0},
-      {65785, 0, 0},
-      {131134, 0, 0},
-      {131319, 0, 0},
-      {196669, 0, 0},
-      {196735, 0, 0},
-      {262209, 0, 0},
-      {262221, 0, 0},
-      {262224, 0, 0},
-      {262225, 0, 0},
-      {262230, 0, 0},
-      {262273, 0, 0},
-      {262275, 0, 0},
-      {262277, 0, 0},
-      {262280, 0, 0},
-      {262286, 0, 0},
-      {262288, 0, 0},
-      {262292, 0, 0},
-      {262334, 0, 0},
-      {327692, 0, 0},
-      {327760, 0, 0},
-      {327761, 0, 0},
-      {327762, 0, 0},
-      {393228, 0, 0},
-      {393295, 0, 0},
-      {393296, 0, 0},
-      {393298, 0, 0},
-      {458764, 0, 0},
-      {458831, 0, 0},
-      {524367, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 17, 3},
-      {0, 5, 31},
-      {0, 11, 32},
-      {0, 33, 12},
-      {0, 34, 20},
-      {0, 16, 27},
-      {0, 35, 23},
-      {0, 37, 36},
-      {0, 14, 18},
-      {0, 39, 38},
-      {0, 7, 30},
-      {0, 8, 25},
-      {0, 40, 15},
-      {0, 13, 2},
-      {0, 1, 29},
-      {0, 19, 41},
-      {0, 43, 42},
-      {0, 28, 44},
-      {0, 46, 45},
-      {0, 22, 21},
-      {0, 47, 24},
-      {0, 48, 26},
-      {0, 10, 6},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 4, 9},
-      {0, 56, 55},
-      {0, 58, 57},
-    }));
-
-    codecs.emplace(SpvOpCompositeInsert, std::move(codec));
-  }
-
-  return codecs;
-}
-
-std::map<uint32_t, std::unique_ptr<HuffmanCodec<std::string>>>
-GetLiteralStringHuffmanCodecs() {
-  std::map<uint32_t, std::unique_ptr<HuffmanCodec<std::string>>> codecs;
-  {
-    std::unique_ptr<HuffmanCodec<std::string>> codec(new HuffmanCodec<std::string>(7, {
-      {"", 0, 0},
-      {"MainPs", 0, 0},
-      {"MainVs", 0, 0},
-      {"kMarkvNoneOfTheAbove", 0, 0},
-      {"main", 0, 0},
-      {"", 2, 3},
-      {"", 1, 5},
-      {"", 4, 6},
-    }));
-
-    codecs.emplace(SpvOpEntryPoint, std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<std::string>> codec(new HuffmanCodec<std::string>(3, {
-      {"", 0, 0},
-      {"GLSL.std.450", 0, 0},
-      {"kMarkvNoneOfTheAbove", 0, 0},
-      {"", 1, 2},
-    }));
-
-    codecs.emplace(SpvOpExtInstImport, std::move(codec));
-  }
-
-  return codecs;
-}
-
-std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>>
-GetNonIdWordHuffmanCodecs() {
-  std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(33, {
-      {0, 0, 0},
-      {4, 0, 0},
-      {8, 0, 0},
-      {10, 0, 0},
-      {26, 0, 0},
-      {29, 0, 0},
-      {31, 0, 0},
-      {37, 0, 0},
-      {40, 0, 0},
-      {43, 0, 0},
-      {46, 0, 0},
-      {49, 0, 0},
-      {66, 0, 0},
-      {67, 0, 0},
-      {68, 0, 0},
-      {69, 0, 0},
-      {71, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 5},
-      {0, 18, 13},
-      {0, 3, 7},
-      {0, 19, 11},
-      {0, 20, 16},
-      {0, 14, 17},
-      {0, 21, 1},
-      {0, 2, 6},
-      {0, 23, 22},
-      {0, 4, 24},
-      {0, 26, 25},
-      {0, 28, 27},
-      {0, 10, 15},
-      {0, 8, 9},
-      {0, 30, 29},
-      {0, 32, 31},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemoryModel, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemoryModel, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {4, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpEntryPoint, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExecutionMode, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {18, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 2},
-      {0, 6, 5},
-      {0, 7, 1},
-      {0, 3, 8},
-      {0, 10, 9},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExecutionMode, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {32, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCapability, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {32, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeInt, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeInt, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {32, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFloat, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 4},
-      {0, 1, 5},
-      {0, 6, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeMatrix, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 2, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeImage, 7), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 7},
-      {0, 6, 8},
-      {0, 1, 4},
-      {0, 2, 9},
-      {0, 10, 3},
-      {0, 12, 11},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(173, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {11, 0, 0},
-      {12, 0, 0},
-      {13, 0, 0},
-      {14, 0, 0},
-      {15, 0, 0},
-      {16, 0, 0},
-      {17, 0, 0},
-      {18, 0, 0},
-      {19, 0, 0},
-      {20, 0, 0},
-      {21, 0, 0},
-      {22, 0, 0},
-      {23, 0, 0},
-      {24, 0, 0},
-      {26, 0, 0},
-      {27, 0, 0},
-      {28, 0, 0},
-      {29, 0, 0},
-      {30, 0, 0},
-      {31, 0, 0},
-      {32, 0, 0},
-      {256, 0, 0},
-      {507307272, 0, 0},
-      {864026611, 0, 0},
-      {981668463, 0, 0},
-      {997553156, 0, 0},
-      {1014330372, 0, 0},
-      {1020708227, 0, 0},
-      {1028443341, 0, 0},
-      {1032953056, 0, 0},
-      {1033463938, 0, 0},
-      {1033463943, 0, 0},
-      {1039998884, 0, 0},
-      {1039998950, 0, 0},
-      {1040187392, 0, 0},
-      {1042401985, 0, 0},
-      {1044220635, 0, 0},
-      {1045622707, 0, 0},
-      {1045622740, 0, 0},
-      {1048576000, 0, 0},
-      {1053609165, 0, 0},
-      {1053790359, 0, 0},
-      {1054448026, 0, 0},
-      {1055437881, 0, 0},
-      {1056300230, 0, 0},
-      {1056964608, 0, 0},
-      {1058056805, 0, 0},
-      {1059286575, 0, 0},
-      {1061158912, 0, 0},
-      {1061997773, 0, 0},
-      {1064514355, 0, 0},
-      {1064854933, 0, 0},
-      {1065353216, 0, 0},
-      {1069547520, 0, 0},
-      {1073741824, 0, 0},
-      {1077936128, 0, 0},
-      {1082130432, 0, 0},
-      {1091567616, 0, 0},
-      {1115422720, 0, 0},
-      {1124073472, 0, 0},
-      {1132396544, 0, 0},
-      {1140850688, 0, 0},
-      {1199562752, 0, 0},
-      {3179067684, 0, 0},
-      {3180973575, 0, 0},
-      {3182651297, 0, 0},
-      {3196448879, 0, 0},
-      {3204448256, 0, 0},
-      {3204993516, 0, 0},
-      {3205248529, 0, 0},
-      {3207137644, 0, 0},
-      {3208642560, 0, 0},
-      {3211081967, 0, 0},
-      {3212836864, 0, 0},
-      {3332128768, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 38, 37},
-      {0, 42, 39},
-      {0, 49, 44},
-      {0, 45, 43},
-      {0, 26, 50},
-      {0, 46, 73},
-      {0, 35, 28},
-      {0, 32, 65},
-      {0, 83, 40},
-      {0, 60, 62},
-      {0, 27, 54},
-      {0, 79, 67},
-      {0, 31, 74},
-      {0, 51, 12},
-      {0, 70, 30},
-      {0, 15, 16},
-      {0, 88, 25},
-      {0, 90, 89},
-      {0, 34, 71},
-      {0, 72, 29},
-      {0, 92, 91},
-      {0, 14, 33},
-      {0, 94, 93},
-      {0, 22, 23},
-      {0, 21, 95},
-      {0, 19, 24},
-      {0, 96, 13},
-      {0, 47, 41},
-      {0, 53, 48},
-      {0, 58, 56},
-      {0, 63, 59},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 81, 80},
-      {0, 84, 82},
-      {0, 52, 20},
-      {0, 97, 69},
-      {0, 99, 98},
-      {0, 18, 10},
-      {0, 68, 61},
-      {0, 17, 100},
-      {0, 102, 101},
-      {0, 11, 36},
-      {0, 104, 103},
-      {0, 86, 105},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 110, 9},
-      {0, 8, 111},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 126, 7},
-      {0, 127, 85},
-      {0, 6, 128},
-      {0, 129, 55},
-      {0, 130, 5},
-      {0, 132, 131},
-      {0, 134, 133},
-      {0, 136, 135},
-      {0, 137, 66},
-      {0, 139, 138},
-      {0, 141, 140},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 146, 57},
-      {0, 147, 64},
-      {0, 148, 4},
-      {0, 149, 2},
-      {0, 151, 150},
-      {0, 152, 3},
-      {0, 154, 153},
-      {0, 156, 155},
-      {0, 158, 157},
-      {0, 159, 1},
-      {0, 160, 87},
-      {0, 162, 161},
-      {0, 164, 163},
-      {0, 166, 165},
-      {0, 168, 167},
-      {0, 170, 169},
-      {0, 172, 171},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 7},
-      {0, 4, 8},
-      {0, 9, 2},
-      {0, 1, 5},
-      {0, 10, 6},
-      {0, 12, 11},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {2, 0, 0},
-      {6, 0, 0},
-      {11, 0, 0},
-      {30, 0, 0},
-      {33, 0, 0},
-      {34, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 8},
-      {0, 9, 1},
-      {0, 3, 10},
-      {0, 6, 11},
-      {0, 12, 2},
-      {0, 7, 5},
-      {0, 14, 13},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDecorate, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(37, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {12, 0, 0},
-      {13, 0, 0},
-      {14, 0, 0},
-      {15, 0, 0},
-      {16, 0, 0},
-      {18, 0, 0},
-      {64, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 17, 11},
-      {0, 10, 13},
-      {0, 12, 14},
-      {0, 21, 20},
-      {0, 9, 22},
-      {0, 19, 15},
-      {0, 8, 23},
-      {0, 18, 24},
-      {0, 25, 7},
-      {0, 5, 6},
-      {0, 26, 16},
-      {0, 27, 4},
-      {0, 28, 3},
-      {0, 30, 29},
-      {0, 31, 2},
-      {0, 33, 32},
-      {0, 35, 34},
-      {0, 1, 36},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDecorate, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {11, 0, 0},
-      {12, 0, 0},
-      {13, 0, 0},
-      {14, 0, 0},
-      {15, 0, 0},
-      {16, 0, 0},
-      {17, 0, 0},
-      {18, 0, 0},
-      {19, 0, 0},
-      {20, 0, 0},
-      {21, 0, 0},
-      {22, 0, 0},
-      {23, 0, 0},
-      {24, 0, 0},
-      {25, 0, 0},
-      {26, 0, 0},
-      {27, 0, 0},
-      {28, 0, 0},
-      {29, 0, 0},
-      {30, 0, 0},
-      {31, 0, 0},
-      {32, 0, 0},
-      {33, 0, 0},
-      {34, 0, 0},
-      {35, 0, 0},
-      {36, 0, 0},
-      {37, 0, 0},
-      {38, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 39, 37},
-      {0, 40, 36},
-      {0, 34, 35},
-      {0, 32, 33},
-      {0, 30, 31},
-      {0, 27, 29},
-      {0, 26, 28},
-      {0, 42, 41},
-      {0, 23, 25},
-      {0, 38, 22},
-      {0, 44, 43},
-      {0, 46, 45},
-      {0, 21, 47},
-      {0, 19, 20},
-      {0, 17, 18},
-      {0, 14, 15},
-      {0, 12, 10},
-      {0, 16, 13},
-      {0, 9, 11},
-      {0, 7, 8},
-      {0, 6, 5},
-      {0, 24, 48},
-      {0, 50, 49},
-      {0, 3, 4},
-      {0, 51, 2},
-      {0, 1, 52},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {4, 0, 0},
-      {7, 0, 0},
-      {35, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 5, 2},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(149, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {16, 0, 0},
-      {28, 0, 0},
-      {32, 0, 0},
-      {36, 0, 0},
-      {40, 0, 0},
-      {44, 0, 0},
-      {48, 0, 0},
-      {60, 0, 0},
-      {64, 0, 0},
-      {76, 0, 0},
-      {80, 0, 0},
-      {84, 0, 0},
-      {88, 0, 0},
-      {92, 0, 0},
-      {96, 0, 0},
-      {100, 0, 0},
-      {108, 0, 0},
-      {112, 0, 0},
-      {120, 0, 0},
-      {124, 0, 0},
-      {128, 0, 0},
-      {132, 0, 0},
-      {136, 0, 0},
-      {140, 0, 0},
-      {144, 0, 0},
-      {148, 0, 0},
-      {152, 0, 0},
-      {156, 0, 0},
-      {160, 0, 0},
-      {172, 0, 0},
-      {176, 0, 0},
-      {192, 0, 0},
-      {204, 0, 0},
-      {208, 0, 0},
-      {224, 0, 0},
-      {236, 0, 0},
-      {240, 0, 0},
-      {248, 0, 0},
-      {256, 0, 0},
-      {272, 0, 0},
-      {288, 0, 0},
-      {292, 0, 0},
-      {296, 0, 0},
-      {300, 0, 0},
-      {304, 0, 0},
-      {316, 0, 0},
-      {320, 0, 0},
-      {332, 0, 0},
-      {336, 0, 0},
-      {348, 0, 0},
-      {352, 0, 0},
-      {364, 0, 0},
-      {368, 0, 0},
-      {372, 0, 0},
-      {376, 0, 0},
-      {384, 0, 0},
-      {392, 0, 0},
-      {400, 0, 0},
-      {416, 0, 0},
-      {424, 0, 0},
-      {432, 0, 0},
-      {448, 0, 0},
-      {460, 0, 0},
-      {464, 0, 0},
-      {468, 0, 0},
-      {472, 0, 0},
-      {476, 0, 0},
-      {480, 0, 0},
-      {488, 0, 0},
-      {492, 0, 0},
-      {496, 0, 0},
-      {512, 0, 0},
-      {640, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 17},
-      {0, 37, 31},
-      {0, 21, 39},
-      {0, 24, 23},
-      {0, 5, 13},
-      {0, 38, 76},
-      {0, 51, 77},
-      {0, 55, 53},
-      {0, 58, 56},
-      {0, 64, 61},
-      {0, 67, 66},
-      {0, 70, 68},
-      {0, 54, 71},
-      {0, 62, 60},
-      {0, 65, 63},
-      {0, 73, 72},
-      {0, 59, 57},
-      {0, 52, 74},
-      {0, 50, 69},
-      {0, 49, 47},
-      {0, 48, 46},
-      {0, 45, 43},
-      {0, 42, 44},
-      {0, 78, 41},
-      {0, 20, 18},
-      {0, 80, 79},
-      {0, 15, 27},
-      {0, 7, 34},
-      {0, 81, 6},
-      {0, 28, 3},
-      {0, 35, 82},
-      {0, 9, 36},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 11, 29},
-      {0, 99, 25},
-      {0, 100, 40},
-      {0, 102, 101},
-      {0, 26, 32},
-      {0, 19, 30},
-      {0, 16, 12},
-      {0, 4, 8},
-      {0, 104, 103},
-      {0, 106, 105},
-      {0, 33, 107},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 22, 112},
-      {0, 113, 10},
-      {0, 115, 114},
-      {0, 75, 116},
-      {0, 118, 117},
-      {0, 119, 1},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 129, 128},
-      {0, 131, 130},
-      {0, 132, 2},
-      {0, 134, 133},
-      {0, 136, 135},
-      {0, 138, 137},
-      {0, 140, 139},
-      {0, 142, 141},
-      {0, 144, 143},
-      {0, 146, 145},
-      {0, 148, 147},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpMemberDecorate, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 6},
-      {0, 4, 7},
-      {0, 8, 3},
-      {0, 9, 5},
-      {0, 1, 10},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 7},
-      {0, 8, 5},
-      {0, 9, 1},
-      {0, 4, 10},
-      {0, 11, 6},
-      {0, 2, 12},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 8},
-      {0, 5, 2},
-      {0, 10, 9},
-      {0, 1, 4},
-      {0, 12, 11},
-      {0, 7, 13},
-      {0, 3, 14},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 5},
-      {0, 9, 7},
-      {0, 10, 3},
-      {0, 11, 2},
-      {0, 6, 1},
-      {0, 13, 12},
-      {0, 4, 14},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 7), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(61, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {11, 0, 0},
-      {12, 0, 0},
-      {13, 0, 0},
-      {14, 0, 0},
-      {15, 0, 0},
-      {16, 0, 0},
-      {17, 0, 0},
-      {18, 0, 0},
-      {19, 0, 0},
-      {20, 0, 0},
-      {21, 0, 0},
-      {22, 0, 0},
-      {23, 0, 0},
-      {24, 0, 0},
-      {27, 0, 0},
-      {28, 0, 0},
-      {29, 0, 0},
-      {30, 0, 0},
-      {31, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 30, 16},
-      {0, 26, 27},
-      {0, 29, 28},
-      {0, 18, 22},
-      {0, 12, 19},
-      {0, 15, 20},
-      {0, 14, 23},
-      {0, 32, 7},
-      {0, 8, 21},
-      {0, 11, 33},
-      {0, 17, 34},
-      {0, 25, 13},
-      {0, 36, 35},
-      {0, 9, 10},
-      {0, 38, 37},
-      {0, 39, 31},
-      {0, 5, 40},
-      {0, 42, 41},
-      {0, 44, 43},
-      {0, 6, 45},
-      {0, 46, 24},
-      {0, 48, 47},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 55, 4},
-      {0, 56, 3},
-      {0, 57, 2},
-      {0, 58, 1},
-      {0, 60, 59},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(63, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {11, 0, 0},
-      {12, 0, 0},
-      {13, 0, 0},
-      {29, 0, 0},
-      {30, 0, 0},
-      {31, 0, 0},
-      {32, 0, 0},
-      {33, 0, 0},
-      {34, 0, 0},
-      {35, 0, 0},
-      {36, 0, 0},
-      {37, 0, 0},
-      {38, 0, 0},
-      {39, 0, 0},
-      {40, 0, 0},
-      {41, 0, 0},
-      {42, 0, 0},
-      {43, 0, 0},
-      {44, 0, 0},
-      {45, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 13, 14},
-      {0, 12, 9},
-      {0, 11, 25},
-      {0, 27, 26},
-      {0, 29, 28},
-      {0, 31, 30},
-      {0, 23, 22},
-      {0, 10, 24},
-      {0, 8, 21},
-      {0, 17, 7},
-      {0, 19, 18},
-      {0, 15, 20},
-      {0, 6, 16},
-      {0, 5, 33},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 41, 40},
-      {0, 43, 42},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 32, 52},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 3, 2},
-      {0, 59, 4},
-      {0, 60, 1},
-      {0, 62, 61},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 5},
-      {0, 3, 2},
-      {0, 6, 4},
-      {0, 8, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {4, 0, 0},
-      {5, 0, 0},
-      {6, 0, 0},
-      {7, 0, 0},
-      {8, 0, 0},
-      {9, 0, 0},
-      {10, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 11},
-      {0, 10, 13},
-      {0, 9, 14},
-      {0, 7, 5},
-      {0, 8, 6},
-      {0, 4, 15},
-      {0, 17, 16},
-      {0, 18, 3},
-      {0, 19, 2},
-      {0, 20, 1},
-      {0, 22, 21},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1, 0, 0},
-      {2, 0, 0},
-      {3, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 2, 6},
-      {0, 7, 1},
-      {0, 4, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {2, 0, 0},
-      {10, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {2, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleDrefExplicitLod, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {0, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSelectionMerge, 1), std::move(codec));
-  }
-
-  return codecs;
-}
-
-std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>>
-GetIdDescriptorHuffmanCodecs() {
-  std::map<std::pair<uint32_t, uint32_t>, std::unique_ptr<HuffmanCodec<uint64_t>>> codecs;
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 4, 6},
-      {0, 1, 7},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(63, {
-      {0, 0, 0},
-      {34183582, 0, 0},
-      {223800276, 0, 0},
-      {295018543, 0, 0},
-      {439764402, 0, 0},
-      {443558693, 0, 0},
-      {583624926, 0, 0},
-      {599185303, 0, 0},
-      {779021139, 0, 0},
-      {1015552308, 0, 0},
-      {1027242654, 0, 0},
-      {1077859090, 0, 0},
-      {1104362365, 0, 0},
-      {1132589448, 0, 0},
-      {1236389532, 0, 0},
-      {1739837626, 0, 0},
-      {1955104493, 0, 0},
-      {2161102232, 0, 0},
-      {2197874825, 0, 0},
-      {2217833278, 0, 0},
-      {2244470522, 0, 0},
-      {2532518896, 0, 0},
-      {2789375411, 0, 0},
-      {3061690214, 0, 0},
-      {3287039847, 0, 0},
-      {3357301402, 0, 0},
-      {3365041621, 0, 0},
-      {3510257966, 0, 0},
-      {3534235309, 0, 0},
-      {4018237905, 0, 0},
-      {4145966869, 0, 0},
-      {4272200782, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 10, 19},
-      {0, 6, 1},
-      {0, 26, 13},
-      {0, 2, 11},
-      {0, 15, 22},
-      {0, 23, 18},
-      {0, 4, 27},
-      {0, 28, 12},
-      {0, 3, 30},
-      {0, 9, 7},
-      {0, 20, 14},
-      {0, 29, 16},
-      {0, 21, 8},
-      {0, 34, 33},
-      {0, 36, 35},
-      {0, 31, 25},
-      {0, 37, 24},
-      {0, 39, 38},
-      {0, 41, 40},
-      {0, 43, 42},
-      {0, 45, 44},
-      {0, 17, 5},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 32, 62},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {4228502127, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(113, {
-      {0, 0, 0},
-      {50998433, 0, 0},
-      {139011596, 0, 0},
-      {181902171, 0, 0},
-      {296981500, 0, 0},
-      {321630747, 0, 0},
-      {416853049, 0, 0},
-      {464259778, 0, 0},
-      {615982737, 0, 0},
-      {669982125, 0, 0},
-      {759277550, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {922996215, 0, 0},
-      {969500141, 0, 0},
-      {1015552308, 0, 0},
-      {1139547465, 0, 0},
-      {1203545131, 0, 0},
-      {1220643281, 0, 0},
-      {1220749418, 0, 0},
-      {1367301635, 0, 0},
-      {1395923345, 0, 0},
-      {1554194368, 0, 0},
-      {1742737136, 0, 0},
-      {1755648697, 0, 0},
-      {1962162282, 0, 0},
-      {1964254745, 0, 0},
-      {2055836767, 0, 0},
-      {2096388952, 0, 0},
-      {2124837447, 0, 0},
-      {2161102232, 0, 0},
-      {2321729979, 0, 0},
-      {2346547796, 0, 0},
-      {2399809085, 0, 0},
-      {2432827426, 0, 0},
-      {2455417440, 0, 0},
-      {2572638469, 0, 0},
-      {2614879967, 0, 0},
-      {2855506940, 0, 0},
-      {2919796598, 0, 0},
-      {2970183398, 0, 0},
-      {2976066508, 0, 0},
-      {3044188332, 0, 0},
-      {3061690214, 0, 0},
-      {3091876332, 0, 0},
-      {3104643263, 0, 0},
-      {3107165180, 0, 0},
-      {3187066832, 0, 0},
-      {3413713311, 0, 0},
-      {3487022798, 0, 0},
-      {3602693817, 0, 0},
-      {3678875745, 0, 0},
-      {3701632935, 0, 0},
-      {3829325073, 0, 0},
-      {4040340620, 0, 0},
-      {4174489262, 0, 0},
-      {4272200782, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 33, 7},
-      {0, 13, 34},
-      {0, 21, 18},
-      {0, 53, 22},
-      {0, 39, 1},
-      {0, 14, 9},
-      {0, 43, 26},
-      {0, 51, 35},
-      {0, 19, 6},
-      {0, 15, 25},
-      {0, 55, 29},
-      {0, 32, 3},
-      {0, 27, 44},
-      {0, 10, 46},
-      {0, 45, 24},
-      {0, 36, 40},
-      {0, 47, 8},
-      {0, 48, 54},
-      {0, 58, 5},
-      {0, 60, 59},
-      {0, 30, 61},
-      {0, 62, 56},
-      {0, 64, 63},
-      {0, 41, 50},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 37, 31},
-      {0, 4, 17},
-      {0, 16, 20},
-      {0, 72, 71},
-      {0, 73, 52},
-      {0, 49, 12},
-      {0, 75, 74},
-      {0, 76, 11},
-      {0, 23, 42},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 84, 83},
-      {0, 85, 28},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 94, 2},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 100, 99},
-      {0, 102, 101},
-      {0, 38, 103},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 57, 112},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
-      {0, 0, 0},
-      {72782198, 0, 0},
-      {139011596, 0, 0},
-      {296981500, 0, 0},
-      {300939750, 0, 0},
-      {401211099, 0, 0},
-      {429277936, 0, 0},
-      {505940164, 0, 0},
-      {538168945, 0, 0},
-      {603915804, 0, 0},
-      {688216667, 0, 0},
-      {706016261, 0, 0},
-      {790502615, 0, 0},
-      {810488476, 0, 0},
-      {993150979, 0, 0},
-      {1203545131, 0, 0},
-      {1206726575, 0, 0},
-      {1265796414, 0, 0},
-      {1314843976, 0, 0},
-      {1367301635, 0, 0},
-      {1378082995, 0, 0},
-      {1410311776, 0, 0},
-      {1443829854, 0, 0},
-      {1448448666, 0, 0},
-      {1468919488, 0, 0},
-      {1496351055, 0, 0},
-      {1619778288, 0, 0},
-      {1684282922, 0, 0},
-      {1848784182, 0, 0},
-      {1901166356, 0, 0},
-      {2095546797, 0, 0},
-      {2096388952, 0, 0},
-      {2162986400, 0, 0},
-      {2197874825, 0, 0},
-      {2246405597, 0, 0},
-      {2250225826, 0, 0},
-      {2282454607, 0, 0},
-      {2328748202, 0, 0},
-      {2348201466, 0, 0},
-      {2597020383, 0, 0},
-      {2633682514, 0, 0},
-      {2817335337, 0, 0},
-      {2855506940, 0, 0},
-      {2936040203, 0, 0},
-      {2955375511, 0, 0},
-      {3122368657, 0, 0},
-      {3154597438, 0, 0},
-      {3184381405, 0, 0},
-      {3187066832, 0, 0},
-      {3233393284, 0, 0},
-      {3251128023, 0, 0},
-      {3260309823, 0, 0},
-      {3441531391, 0, 0},
-      {3496407048, 0, 0},
-      {3582002820, 0, 0},
-      {3647586740, 0, 0},
-      {3653838348, 0, 0},
-      {3730093054, 0, 0},
-      {3759072440, 0, 0},
-      {3928764629, 0, 0},
-      {3969279737, 0, 0},
-      {3994511488, 0, 0},
-      {4026740269, 0, 0},
-      {4274214049, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 43, 23},
-      {0, 5, 24},
-      {0, 9, 8},
-      {0, 36, 21},
-      {0, 13, 46},
-      {0, 7, 12},
-      {0, 35, 20},
-      {0, 61, 59},
-      {0, 22, 29},
-      {0, 38, 62},
-      {0, 56, 45},
-      {0, 6, 48},
-      {0, 33, 30},
-      {0, 14, 58},
-      {0, 34, 28},
-      {0, 51, 40},
-      {0, 63, 55},
-      {0, 25, 16},
-      {0, 17, 11},
-      {0, 53, 52},
-      {0, 65, 27},
-      {0, 39, 41},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 10, 4},
-      {0, 37, 18},
-      {0, 60, 47},
-      {0, 1, 32},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 57, 26},
-      {0, 74, 31},
-      {0, 76, 75},
-      {0, 77, 44},
-      {0, 78, 15},
-      {0, 79, 54},
-      {0, 81, 80},
-      {0, 82, 49},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 89, 19},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 2, 96},
-      {0, 98, 97},
-      {0, 100, 99},
-      {0, 102, 101},
-      {0, 104, 103},
-      {0, 106, 105},
-      {0, 3, 107},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 114, 50},
-      {0, 116, 115},
-      {0, 118, 117},
-      {0, 120, 119},
-      {0, 122, 121},
-      {0, 124, 123},
-      {0, 64, 42},
-      {0, 126, 125},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(93, {
-      {0, 0, 0},
-      {99347751, 0, 0},
-      {102542696, 0, 0},
-      {107497541, 0, 0},
-      {112452386, 0, 0},
-      {139011596, 0, 0},
-      {296981500, 0, 0},
-      {429277936, 0, 0},
-      {451957774, 0, 0},
-      {508217552, 0, 0},
-      {573901046, 0, 0},
-      {774727851, 0, 0},
-      {801484894, 0, 0},
-      {920604853, 0, 0},
-      {925559698, 0, 0},
-      {1022915255, 0, 0},
-      {1209418480, 0, 0},
-      {1287937401, 0, 0},
-      {1319785741, 0, 0},
-      {1392080469, 0, 0},
-      {1538342947, 0, 0},
-      {1541020250, 0, 0},
-      {1587209598, 0, 0},
-      {1594733696, 0, 0},
-      {1631434666, 0, 0},
-      {1636389511, 0, 0},
-      {1684282922, 0, 0},
-      {1859128680, 0, 0},
-      {1901166356, 0, 0},
-      {2004567202, 0, 0},
-      {2119793999, 0, 0},
-      {2280400314, 0, 0},
-      {2538917932, 0, 0},
-      {2677264274, 0, 0},
-      {2683080096, 0, 0},
-      {2854085372, 0, 0},
-      {2879917501, 0, 0},
-      {3059119137, 0, 0},
-      {3174324790, 0, 0},
-      {3194725903, 0, 0},
-      {3358097187, 0, 0},
-      {3547456240, 0, 0},
-      {3614752756, 0, 0},
-      {3753486980, 0, 0},
-      {3811268385, 0, 0},
-      {3953733490, 0, 0},
-      {3990925720, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 23, 22},
-      {0, 36, 31},
-      {0, 17, 40},
-      {0, 27, 19},
-      {0, 35, 33},
-      {0, 30, 38},
-      {0, 42, 39},
-      {0, 46, 32},
-      {0, 13, 12},
-      {0, 44, 14},
-      {0, 29, 11},
-      {0, 10, 18},
-      {0, 15, 37},
-      {0, 1, 4},
-      {0, 45, 2},
-      {0, 21, 28},
-      {0, 8, 5},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 53, 52},
-      {0, 54, 16},
-      {0, 55, 25},
-      {0, 56, 3},
-      {0, 58, 57},
-      {0, 59, 26},
-      {0, 20, 7},
-      {0, 61, 60},
-      {0, 62, 24},
-      {0, 41, 63},
-      {0, 65, 64},
-      {0, 9, 34},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 76, 43},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 47, 91},
-      {0, 92, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpExtInst, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {166253838, 0, 0},
-      {679771963, 0, 0},
-      {1247793383, 0, 0},
-      {2261697609, 0, 0},
-      {2263349224, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 8},
-      {0, 9, 1},
-      {0, 3, 5},
-      {0, 11, 10},
-      {0, 2, 12},
-      {0, 7, 6},
-      {0, 14, 13},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {789872778, 0, 0},
-      {1415510495, 0, 0},
-      {1951208733, 0, 0},
-      {2430404313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 5},
-      {0, 4, 6},
-      {0, 7, 1},
-      {0, 3, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeVector, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {1389644742, 0, 0},
-      {3232633974, 0, 0},
-      {3278176820, 0, 0},
-      {3648138580, 0, 0},
-      {3687777340, 0, 0},
-      {3694383800, 0, 0},
-      {3697687030, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 4},
-      {0, 9, 6},
-      {0, 10, 8},
-      {0, 2, 11},
-      {0, 12, 3},
-      {0, 1, 13},
-      {0, 14, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {144116905, 0, 0},
-      {827246872, 0, 0},
-      {1545298048, 0, 0},
-      {2715370488, 0, 0},
-      {2798552666, 0, 0},
-      {3812456892, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 8, 6},
-      {0, 9, 7},
-      {0, 1, 10},
-      {0, 11, 4},
-      {0, 5, 12},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeArray, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(67, {
-      {0, 0, 0},
-      {40653745, 0, 0},
-      {119981689, 0, 0},
-      {153085016, 0, 0},
-      {451382997, 0, 0},
-      {545678922, 0, 0},
-      {899570100, 0, 0},
-      {929101967, 0, 0},
-      {1070791291, 0, 0},
-      {1100599986, 0, 0},
-      {1103903216, 0, 0},
-      {1154919607, 0, 0},
-      {1199157863, 0, 0},
-      {1258105452, 0, 0},
-      {1369578001, 0, 0},
-      {1372881231, 0, 0},
-      {1674803691, 0, 0},
-      {1677700667, 0, 0},
-      {1989520052, 0, 0},
-      {2593884753, 0, 0},
-      {2664825925, 0, 0},
-      {2924146124, 0, 0},
-      {2926633629, 0, 0},
-      {3249265647, 0, 0},
-      {3345288309, 0, 0},
-      {3410158390, 0, 0},
-      {3489360962, 0, 0},
-      {3495967422, 0, 0},
-      {3504981554, 0, 0},
-      {3705139860, 0, 0},
-      {3822983876, 0, 0},
-      {4141567741, 0, 0},
-      {4234287173, 0, 0},
-      {4240893633, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 15, 23},
-      {0, 20, 17},
-      {0, 32, 22},
-      {0, 19, 12},
-      {0, 13, 3},
-      {0, 30, 27},
-      {0, 4, 35},
-      {0, 24, 36},
-      {0, 31, 37},
-      {0, 33, 38},
-      {0, 39, 7},
-      {0, 6, 40},
-      {0, 41, 29},
-      {0, 14, 42},
-      {0, 43, 28},
-      {0, 10, 44},
-      {0, 45, 18},
-      {0, 26, 46},
-      {0, 5, 47},
-      {0, 48, 2},
-      {0, 49, 9},
-      {0, 50, 16},
-      {0, 34, 25},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 8, 21},
-      {0, 1, 11},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {3278176820, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 6},
-      {0, 2, 7},
-      {0, 3, 8},
-      {0, 9, 1},
-      {0, 5, 10},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {2320303498, 0, 0},
-      {3232633974, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 7},
-      {0, 2, 8},
-      {0, 4, 9},
-      {0, 10, 3},
-      {0, 1, 6},
-      {0, 12, 11},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 6},
-      {0, 1, 7},
-      {0, 3, 4},
-      {0, 8, 2},
-      {0, 10, 9},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 6},
-      {0, 3, 7},
-      {0, 5, 4},
-      {0, 8, 1},
-      {0, 10, 9},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2263349224, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 1, 6},
-      {0, 2, 7},
-      {0, 8, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 1, 6},
-      {0, 2, 7},
-      {0, 8, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 4, 6},
-      {0, 7, 1},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 7), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 1, 6},
-      {0, 7, 4},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 8), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 4},
-      {0, 3, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 9), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 3},
-      {0, 1, 6},
-      {0, 4, 7},
-      {0, 8, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 10), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 1, 6},
-      {0, 7, 4},
-      {0, 8, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 11), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 4, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 12), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 13), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 3, 6},
-      {0, 7, 1},
-      {0, 8, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 14), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 5, 3},
-      {0, 6, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 15), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 2, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 16), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 4, 6},
-      {0, 7, 1},
-      {0, 8, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 17), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 18), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 3, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 19), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 20), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 21), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 1},
-      {0, 2, 6},
-      {0, 3, 7},
-      {0, 8, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 22), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 5},
-      {0, 2, 6},
-      {0, 4, 7},
-      {0, 8, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 23), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2160380860, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 4},
-      {0, 1, 7},
-      {0, 2, 8},
-      {0, 3, 9},
-      {0, 10, 5},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 24), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 5},
-      {0, 2, 6},
-      {0, 4, 7},
-      {0, 8, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 25), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 26), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 27), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 28), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 29), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 30), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 3},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 31), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 32), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 33), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 3},
-      {0, 2, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 34), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 35), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 36), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 37), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1389644742, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 38), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {3697687030, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 39), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 40), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 41), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 42), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 43), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 44), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 45), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 46), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 47), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 48), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 49), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 50), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeStruct, 51), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(101, {
-      {0, 0, 0},
-      {85880059, 0, 0},
-      {135486769, 0, 0},
-      {304448521, 0, 0},
-      {436416061, 0, 0},
-      {440421571, 0, 0},
-      {450406196, 0, 0},
-      {503094540, 0, 0},
-      {543621065, 0, 0},
-      {626892406, 0, 0},
-      {628544021, 0, 0},
-      {827698488, 0, 0},
-      {869050696, 0, 0},
-      {907126242, 0, 0},
-      {908777857, 0, 0},
-      {910429472, 0, 0},
-      {1113409935, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1322549027, 0, 0},
-      {1784441183, 0, 0},
-      {2080953106, 0, 0},
-      {2194691858, 0, 0},
-      {2448331885, 0, 0},
-      {2466255445, 0, 0},
-      {2468230023, 0, 0},
-      {2547657777, 0, 0},
-      {2549309392, 0, 0},
-      {2550961007, 0, 0},
-      {2894051250, 0, 0},
-      {2929019254, 0, 0},
-      {2934934694, 0, 0},
-      {2936586309, 0, 0},
-      {2938237924, 0, 0},
-      {3077271274, 0, 0},
-      {3092528578, 0, 0},
-      {3094180193, 0, 0},
-      {3094857332, 0, 0},
-      {3095831808, 0, 0},
-      {3183924418, 0, 0},
-      {3207966516, 0, 0},
-      {3282979782, 0, 0},
-      {3433956341, 0, 0},
-      {3561562003, 0, 0},
-      {3563213618, 0, 0},
-      {3564865233, 0, 0},
-      {3585511591, 0, 0},
-      {4028622909, 0, 0},
-      {4039938779, 0, 0},
-      {4050155669, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 16, 25},
-      {0, 50, 1},
-      {0, 42, 35},
-      {0, 31, 41},
-      {0, 4, 43},
-      {0, 9, 10},
-      {0, 3, 30},
-      {0, 52, 47},
-      {0, 12, 53},
-      {0, 55, 54},
-      {0, 36, 56},
-      {0, 49, 57},
-      {0, 6, 58},
-      {0, 34, 33},
-      {0, 59, 26},
-      {0, 21, 32},
-      {0, 60, 15},
-      {0, 24, 61},
-      {0, 62, 38},
-      {0, 22, 2},
-      {0, 37, 7},
-      {0, 63, 46},
-      {0, 14, 13},
-      {0, 64, 5},
-      {0, 65, 45},
-      {0, 66, 19},
-      {0, 18, 67},
-      {0, 17, 20},
-      {0, 68, 11},
-      {0, 8, 69},
-      {0, 70, 39},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 40, 75},
-      {0, 76, 23},
-      {0, 78, 77},
-      {0, 29, 79},
-      {0, 28, 80},
-      {0, 27, 48},
-      {0, 82, 81},
-      {0, 51, 83},
-      {0, 84, 44},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 100, 99},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(65, {
-      {0, 0, 0},
-      {119981689, 0, 0},
-      {162255877, 0, 0},
-      {451382997, 0, 0},
-      {545678922, 0, 0},
-      {679771963, 0, 0},
-      {789872778, 0, 0},
-      {1100599986, 0, 0},
-      {1103903216, 0, 0},
-      {1154919607, 0, 0},
-      {1343794461, 0, 0},
-      {1415510495, 0, 0},
-      {1674803691, 0, 0},
-      {1951208733, 0, 0},
-      {1989520052, 0, 0},
-      {2160380860, 0, 0},
-      {2263349224, 0, 0},
-      {2320303498, 0, 0},
-      {2924146124, 0, 0},
-      {2984325996, 0, 0},
-      {3334207724, 0, 0},
-      {3345288309, 0, 0},
-      {3410158390, 0, 0},
-      {3489360962, 0, 0},
-      {3495967422, 0, 0},
-      {3504981554, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {3866587616, 0, 0},
-      {3868239231, 0, 0},
-      {3869890846, 0, 0},
-      {3998230222, 0, 0},
-      {4240893633, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 3},
-      {0, 6, 24},
-      {0, 11, 7},
-      {0, 32, 21},
-      {0, 27, 34},
-      {0, 35, 25},
-      {0, 36, 8},
-      {0, 26, 31},
-      {0, 14, 15},
-      {0, 28, 37},
-      {0, 1, 23},
-      {0, 39, 38},
-      {0, 12, 40},
-      {0, 22, 41},
-      {0, 10, 16},
-      {0, 43, 42},
-      {0, 29, 44},
-      {0, 2, 45},
-      {0, 46, 19},
-      {0, 48, 47},
-      {0, 18, 49},
-      {0, 50, 30},
-      {0, 9, 33},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 13, 55},
-      {0, 17, 56},
-      {0, 5, 57},
-      {0, 59, 58},
-      {0, 60, 20},
-      {0, 62, 61},
-      {0, 64, 63},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypePointer, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(99, {
-      {0, 0, 0},
-      {75986790, 0, 0},
-      {95470391, 0, 0},
-      {170378107, 0, 0},
-      {172029722, 0, 0},
-      {204234270, 0, 0},
-      {205885885, 0, 0},
-      {244668133, 0, 0},
-      {265778447, 0, 0},
-      {616435646, 0, 0},
-      {618087261, 0, 0},
-      {753954113, 0, 0},
-      {1000070091, 0, 0},
-      {1308462133, 0, 0},
-      {1671139745, 0, 0},
-      {1774874546, 0, 0},
-      {1776526161, 0, 0},
-      {1887808856, 0, 0},
-      {1889460471, 0, 0},
-      {1917966999, 0, 0},
-      {2044728014, 0, 0},
-      {2192810893, 0, 0},
-      {2293247016, 0, 0},
-      {2503194620, 0, 0},
-      {2605012269, 0, 0},
-      {2608484640, 0, 0},
-      {2615111110, 0, 0},
-      {2668769415, 0, 0},
-      {2759951687, 0, 0},
-      {2761603302, 0, 0},
-      {2856623532, 0, 0},
-      {2945369269, 0, 0},
-      {2956189845, 0, 0},
-      {3085119011, 0, 0},
-      {3367313400, 0, 0},
-      {3447882276, 0, 0},
-      {3633746133, 0, 0},
-      {3635397748, 0, 0},
-      {3710645347, 0, 0},
-      {3712296962, 0, 0},
-      {3715846592, 0, 0},
-      {3727494858, 0, 0},
-      {3747079365, 0, 0},
-      {3748965853, 0, 0},
-      {3750617468, 0, 0},
-      {4018820793, 0, 0},
-      {4022124023, 0, 0},
-      {4024173916, 0, 0},
-      {4215670524, 0, 0},
-      {4217322139, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 10, 9},
-      {0, 31, 24},
-      {0, 40, 13},
-      {0, 45, 33},
-      {0, 34, 46},
-      {0, 43, 38},
-      {0, 44, 15},
-      {0, 11, 30},
-      {0, 21, 6},
-      {0, 47, 3},
-      {0, 51, 16},
-      {0, 14, 52},
-      {0, 8, 53},
-      {0, 35, 5},
-      {0, 55, 54},
-      {0, 56, 26},
-      {0, 20, 57},
-      {0, 39, 19},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 4, 62},
-      {0, 2, 63},
-      {0, 25, 7},
-      {0, 64, 27},
-      {0, 12, 22},
-      {0, 65, 48},
-      {0, 41, 42},
-      {0, 17, 23},
-      {0, 49, 66},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 18, 75},
-      {0, 37, 32},
-      {0, 76, 36},
-      {0, 78, 77},
-      {0, 79, 28},
-      {0, 81, 80},
-      {0, 82, 29},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 91, 50},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 1, 96},
-      {0, 98, 97},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {545678922, 0, 0},
-      {679771963, 0, 0},
-      {899570100, 0, 0},
-      {929101967, 0, 0},
-      {1100599986, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3056042030, 0, 0},
-      {3334207724, 0, 0},
-      {3357250579, 0, 0},
-      {3705139860, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 3},
-      {0, 10, 13},
-      {0, 4, 15},
-      {0, 16, 11},
-      {0, 17, 1},
-      {0, 14, 12},
-      {0, 19, 18},
-      {0, 21, 20},
-      {0, 7, 6},
-      {0, 9, 22},
-      {0, 24, 23},
-      {0, 25, 2},
-      {0, 26, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {283209196, 0, 0},
-      {436416061, 0, 0},
-      {679771963, 0, 0},
-      {789872778, 0, 0},
-      {815757910, 0, 0},
-      {827698488, 0, 0},
-      {1164221089, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1525861001, 0, 0},
-      {1579585816, 0, 0},
-      {1675764636, 0, 0},
-      {1824016656, 0, 0},
-      {1951208733, 0, 0},
-      {1991787192, 0, 0},
-      {2180701723, 0, 0},
-      {2194691858, 0, 0},
-      {2320303498, 0, 0},
-      {2881886868, 0, 0},
-      {2926633629, 0, 0},
-      {3249265647, 0, 0},
-      {3334207724, 0, 0},
-      {3472123498, 0, 0},
-      {3674863070, 0, 0},
-      {4050155669, 0, 0},
-      {4141567741, 0, 0},
-      {4155122613, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 24, 7},
-      {0, 17, 1},
-      {0, 4, 15},
-      {0, 11, 16},
-      {0, 28, 30},
-      {0, 25, 20},
-      {0, 14, 31},
-      {0, 32, 26},
-      {0, 12, 5},
-      {0, 2, 22},
-      {0, 33, 13},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 40, 21},
-      {0, 29, 18},
-      {0, 27, 41},
-      {0, 43, 42},
-      {0, 19, 44},
-      {0, 45, 23},
-      {0, 6, 3},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 10, 8},
-      {0, 53, 52},
-      {0, 9, 54},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 9},
-      {0, 10, 6},
-      {0, 1, 5},
-      {0, 11, 3},
-      {0, 12, 7},
-      {0, 13, 2},
-      {0, 15, 14},
-      {0, 16, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(17, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1951208733, 0, 0},
-      {2194691858, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 5},
-      {0, 10, 9},
-      {0, 11, 6},
-      {0, 7, 12},
-      {0, 1, 3},
-      {0, 2, 13},
-      {0, 15, 14},
-      {0, 4, 16},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 6},
-      {0, 5, 7},
-      {0, 2, 8},
-      {0, 1, 9},
-      {0, 10, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1951208733, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 6},
-      {0, 4, 7},
-      {0, 8, 5},
-      {0, 3, 9},
-      {0, 1, 10},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {789872778, 0, 0},
-      {827698488, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 1},
-      {0, 4, 6},
-      {0, 3, 7},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 7), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {543621065, 0, 0},
-      {827698488, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 8), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {827698488, 0, 0},
-      {1951208733, 0, 0},
-      {3095831808, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 4},
-      {0, 3, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 9), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1296054774, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 10), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {1296054774, 0, 0},
-      {2320303498, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 1, 4},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 11), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {789872778, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 4, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 12), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {789872778, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 1},
-      {0, 4, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 13), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 14), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpTypeFunction, 15), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {789872778, 0, 0},
-      {1951208733, 0, 0},
-      {2430404313, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 1, 5},
-      {0, 2, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(183, {
-      {0, 0, 0},
-      {51041423, 0, 0},
-      {52882140, 0, 0},
-      {72782198, 0, 0},
-      {142465290, 0, 0},
-      {144116905, 0, 0},
-      {158160339, 0, 0},
-      {169135842, 0, 0},
-      {210116709, 0, 0},
-      {290391815, 0, 0},
-      {296981500, 0, 0},
-      {385229009, 0, 0},
-      {438318340, 0, 0},
-      {529742207, 0, 0},
-      {628331516, 0, 0},
-      {677668732, 0, 0},
-      {778500192, 0, 0},
-      {825595257, 0, 0},
-      {910398460, 0, 0},
-      {917019124, 0, 0},
-      {959681532, 0, 0},
-      {1031290113, 0, 0},
-      {1039111164, 0, 0},
-      {1064945649, 0, 0},
-      {1087394637, 0, 0},
-      {1092948665, 0, 0},
-      {1156369516, 0, 0},
-      {1158021131, 0, 0},
-      {1172110445, 0, 0},
-      {1304296041, 0, 0},
-      {1400019344, 0, 0},
-      {1450415100, 0, 0},
-      {1452222566, 0, 0},
-      {1543646433, 0, 0},
-      {1543672828, 0, 0},
-      {1612361408, 0, 0},
-      {1622381564, 0, 0},
-      {1691572958, 0, 0},
-      {1755648697, 0, 0},
-      {1782996825, 0, 0},
-      {1784648440, 0, 0},
-      {1930923350, 0, 0},
-      {1939359710, 0, 0},
-      {1971252067, 0, 0},
-      {1979847999, 0, 0},
-      {2078849875, 0, 0},
-      {2113115132, 0, 0},
-      {2135340676, 0, 0},
-      {2170273742, 0, 0},
-      {2268204687, 0, 0},
-      {2285081596, 0, 0},
-      {2318200267, 0, 0},
-      {2321729979, 0, 0},
-      {2326636627, 0, 0},
-      {2444465148, 0, 0},
-      {2466126792, 0, 0},
-      {2490492987, 0, 0},
-      {2524697596, 0, 0},
-      {2557550659, 0, 0},
-      {2678954464, 0, 0},
-      {2705477184, 0, 0},
-      {2715370488, 0, 0},
-      {2732195517, 0, 0},
-      {2775815164, 0, 0},
-      {2796901051, 0, 0},
-      {2798552666, 0, 0},
-      {2855506940, 0, 0},
-      {2860348412, 0, 0},
-      {2922615804, 0, 0},
-      {2937761472, 0, 0},
-      {2944827576, 0, 0},
-      {3092754101, 0, 0},
-      {3107165180, 0, 0},
-      {3168953855, 0, 0},
-      {3184177968, 0, 0},
-      {3202349435, 0, 0},
-      {3266548732, 0, 0},
-      {3332104493, 0, 0},
-      {3362723943, 0, 0},
-      {3571454885, 0, 0},
-      {3712763835, 0, 0},
-      {3743748793, 0, 0},
-      {3810805277, 0, 0},
-      {3912967080, 0, 0},
-      {3929248764, 0, 0},
-      {3958731802, 0, 0},
-      {3997952447, 0, 0},
-      {4016096296, 0, 0},
-      {4106658327, 0, 0},
-      {4172568578, 0, 0},
-      {4198082194, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 35, 16},
-      {0, 49, 42},
-      {0, 86, 69},
-      {0, 53, 30},
-      {0, 45, 89},
-      {0, 50, 68},
-      {0, 73, 71},
-      {0, 17, 46},
-      {0, 14, 81},
-      {0, 63, 44},
-      {0, 12, 3},
-      {0, 72, 31},
-      {0, 55, 67},
-      {0, 36, 19},
-      {0, 22, 88},
-      {0, 9, 70},
-      {0, 93, 23},
-      {0, 95, 94},
-      {0, 47, 91},
-      {0, 34, 32},
-      {0, 97, 96},
-      {0, 41, 61},
-      {0, 99, 98},
-      {0, 37, 1},
-      {0, 77, 100},
-      {0, 51, 60},
-      {0, 101, 79},
-      {0, 6, 2},
-      {0, 11, 7},
-      {0, 24, 21},
-      {0, 43, 28},
-      {0, 59, 56},
-      {0, 75, 62},
-      {0, 80, 78},
-      {0, 87, 83},
-      {0, 18, 15},
-      {0, 102, 38},
-      {0, 104, 103},
-      {0, 85, 90},
-      {0, 76, 25},
-      {0, 29, 105},
-      {0, 107, 106},
-      {0, 58, 52},
-      {0, 109, 108},
-      {0, 57, 110},
-      {0, 112, 111},
-      {0, 114, 113},
-      {0, 115, 33},
-      {0, 74, 116},
-      {0, 118, 117},
-      {0, 120, 119},
-      {0, 122, 121},
-      {0, 124, 123},
-      {0, 126, 125},
-      {0, 128, 127},
-      {0, 130, 129},
-      {0, 131, 13},
-      {0, 54, 27},
-      {0, 133, 132},
-      {0, 48, 40},
-      {0, 5, 8},
-      {0, 82, 134},
-      {0, 26, 135},
-      {0, 39, 4},
-      {0, 136, 64},
-      {0, 138, 137},
-      {0, 140, 139},
-      {0, 84, 141},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 149, 148},
-      {0, 20, 150},
-      {0, 65, 151},
-      {0, 66, 152},
-      {0, 153, 10},
-      {0, 155, 154},
-      {0, 157, 156},
-      {0, 159, 158},
-      {0, 161, 160},
-      {0, 163, 162},
-      {0, 165, 164},
-      {0, 167, 166},
-      {0, 169, 168},
-      {0, 170, 92},
-      {0, 172, 171},
-      {0, 174, 173},
-      {0, 176, 175},
-      {0, 178, 177},
-      {0, 180, 179},
-      {0, 182, 181},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstant, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1247793383, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 5},
-      {0, 4, 6},
-      {0, 1, 3},
-      {0, 8, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
-      {0, 0, 0},
-      {15502752, 0, 0},
-      {46736908, 0, 0},
-      {139011596, 0, 0},
-      {149720480, 0, 0},
-      {249378857, 0, 0},
-      {251209228, 0, 0},
-      {503145996, 0, 0},
-      {836581417, 0, 0},
-      {882718761, 0, 0},
-      {1289566249, 0, 0},
-      {1325348861, 0, 0},
-      {1558001705, 0, 0},
-      {1646147798, 0, 0},
-      {1679946323, 0, 0},
-      {1766401548, 0, 0},
-      {1992893964, 0, 0},
-      {2123388694, 0, 0},
-      {2162986400, 0, 0},
-      {2580096524, 0, 0},
-      {2598189097, 0, 0},
-      {2683080096, 0, 0},
-      {2698156268, 0, 0},
-      {2763960513, 0, 0},
-      {3015046341, 0, 0},
-      {3133016299, 0, 0},
-      {3251128023, 0, 0},
-      {3504158761, 0, 0},
-      {3535289452, 0, 0},
-      {3536941067, 0, 0},
-      {3538592682, 0, 0},
-      {3540244297, 0, 0},
-      {3541895912, 0, 0},
-      {3570219049, 0, 0},
-      {3653838348, 0, 0},
-      {3764205609, 0, 0},
-      {3882634684, 0, 0},
-      {3913885196, 0, 0},
-      {3982047273, 0, 0},
-      {4024252457, 0, 0},
-      {4243119782, 0, 0},
-      {4255182614, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 4},
-      {0, 39, 2},
-      {0, 38, 10},
-      {0, 29, 41},
-      {0, 23, 28},
-      {0, 9, 24},
-      {0, 44, 43},
-      {0, 45, 6},
-      {0, 20, 12},
-      {0, 18, 33},
-      {0, 19, 16},
-      {0, 7, 46},
-      {0, 48, 47},
-      {0, 5, 49},
-      {0, 13, 11},
-      {0, 17, 14},
-      {0, 25, 22},
-      {0, 40, 36},
-      {0, 1, 50},
-      {0, 31, 30},
-      {0, 51, 32},
-      {0, 42, 52},
-      {0, 54, 53},
-      {0, 55, 15},
-      {0, 37, 56},
-      {0, 57, 34},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 35, 21},
-      {0, 62, 26},
-      {0, 64, 63},
-      {0, 65, 27},
-      {0, 3, 66},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(65, {
-      {0, 0, 0},
-      {142465290, 0, 0},
-      {158160339, 0, 0},
-      {169135842, 0, 0},
-      {210116709, 0, 0},
-      {296981500, 0, 0},
-      {615748604, 0, 0},
-      {910398460, 0, 0},
-      {959681532, 0, 0},
-      {1039111164, 0, 0},
-      {1087394637, 0, 0},
-      {1156369516, 0, 0},
-      {1450415100, 0, 0},
-      {1543672828, 0, 0},
-      {2100532220, 0, 0},
-      {2170273742, 0, 0},
-      {2285081596, 0, 0},
-      {2326636627, 0, 0},
-      {2444465148, 0, 0},
-      {2732195517, 0, 0},
-      {2763232252, 0, 0},
-      {2796901051, 0, 0},
-      {2855506940, 0, 0},
-      {2922615804, 0, 0},
-      {2937761472, 0, 0},
-      {3202349435, 0, 0},
-      {3362723943, 0, 0},
-      {3712763835, 0, 0},
-      {3810805277, 0, 0},
-      {3929248764, 0, 0},
-      {4016096296, 0, 0},
-      {4172568578, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 12, 23},
-      {0, 13, 6},
-      {0, 20, 14},
-      {0, 15, 24},
-      {0, 17, 28},
-      {0, 16, 31},
-      {0, 7, 34},
-      {0, 9, 32},
-      {0, 36, 35},
-      {0, 38, 37},
-      {0, 40, 39},
-      {0, 2, 8},
-      {0, 10, 3},
-      {0, 25, 19},
-      {0, 27, 26},
-      {0, 33, 30},
-      {0, 11, 41},
-      {0, 1, 21},
-      {0, 18, 42},
-      {0, 44, 43},
-      {0, 46, 45},
-      {0, 48, 47},
-      {0, 29, 49},
-      {0, 4, 50},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 59, 5},
-      {0, 61, 60},
-      {0, 62, 22},
-      {0, 64, 63},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {52882140, 0, 0},
-      {210116709, 0, 0},
-      {296981500, 0, 0},
-      {385229009, 0, 0},
-      {615748604, 0, 0},
-      {910398460, 0, 0},
-      {959681532, 0, 0},
-      {1031290113, 0, 0},
-      {1039111164, 0, 0},
-      {1172110445, 0, 0},
-      {1450415100, 0, 0},
-      {1543672828, 0, 0},
-      {1622381564, 0, 0},
-      {1782996825, 0, 0},
-      {1971252067, 0, 0},
-      {2100532220, 0, 0},
-      {2268204687, 0, 0},
-      {2326636627, 0, 0},
-      {2444465148, 0, 0},
-      {2490492987, 0, 0},
-      {2678954464, 0, 0},
-      {2763232252, 0, 0},
-      {2855506940, 0, 0},
-      {2922615804, 0, 0},
-      {3912967080, 0, 0},
-      {3929248764, 0, 0},
-      {4172568578, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 11, 24},
-      {0, 12, 5},
-      {0, 22, 16},
-      {0, 18, 17},
-      {0, 30, 27},
-      {0, 6, 13},
-      {0, 9, 28},
-      {0, 32, 31},
-      {0, 34, 33},
-      {0, 7, 35},
-      {0, 4, 1},
-      {0, 10, 8},
-      {0, 20, 15},
-      {0, 25, 21},
-      {0, 36, 29},
-      {0, 19, 37},
-      {0, 39, 38},
-      {0, 41, 40},
-      {0, 43, 42},
-      {0, 26, 44},
-      {0, 45, 2},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 50, 14},
-      {0, 51, 3},
-      {0, 53, 52},
-      {0, 54, 23},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
-      {0, 0, 0},
-      {210116709, 0, 0},
-      {296981500, 0, 0},
-      {615748604, 0, 0},
-      {910398460, 0, 0},
-      {959681532, 0, 0},
-      {1039111164, 0, 0},
-      {1092948665, 0, 0},
-      {1450415100, 0, 0},
-      {1543672828, 0, 0},
-      {1612361408, 0, 0},
-      {2100532220, 0, 0},
-      {2326636627, 0, 0},
-      {2444465148, 0, 0},
-      {2524697596, 0, 0},
-      {2763232252, 0, 0},
-      {2855506940, 0, 0},
-      {3929248764, 0, 0},
-      {4172568578, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 7},
-      {0, 9, 3},
-      {0, 15, 11},
-      {0, 10, 21},
-      {0, 18, 12},
-      {0, 4, 20},
-      {0, 22, 19},
-      {0, 23, 6},
-      {0, 14, 24},
-      {0, 5, 25},
-      {0, 27, 26},
-      {0, 28, 17},
-      {0, 30, 29},
-      {0, 31, 13},
-      {0, 1, 32},
-      {0, 34, 33},
-      {0, 16, 35},
-      {0, 2, 36},
-      {0, 38, 37},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
-      {0, 0, 0},
-      {296981500, 0, 0},
-      {615748604, 0, 0},
-      {673708384, 0, 0},
-      {959681532, 0, 0},
-      {1039111164, 0, 0},
-      {1450415100, 0, 0},
-      {1543672828, 0, 0},
-      {1939359710, 0, 0},
-      {2100532220, 0, 0},
-      {2113115132, 0, 0},
-      {2326636627, 0, 0},
-      {2444465148, 0, 0},
-      {2763232252, 0, 0},
-      {2855506940, 0, 0},
-      {3929248764, 0, 0},
-      {4172568578, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 18, 3},
-      {0, 6, 19},
-      {0, 12, 4},
-      {0, 17, 2},
-      {0, 9, 7},
-      {0, 20, 13},
-      {0, 11, 8},
-      {0, 10, 16},
-      {0, 21, 15},
-      {0, 5, 22},
-      {0, 24, 23},
-      {0, 26, 25},
-      {0, 28, 27},
-      {0, 29, 1},
-      {0, 31, 30},
-      {0, 33, 32},
-      {0, 34, 14},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpConstantComposite, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
-      {0, 0, 0},
-      {545678922, 0, 0},
-      {679771963, 0, 0},
-      {929101967, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3056042030, 0, 0},
-      {3334207724, 0, 0},
-      {3357250579, 0, 0},
-      {3705139860, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 11},
-      {0, 9, 3},
-      {0, 1, 13},
-      {0, 14, 10},
-      {0, 12, 15},
-      {0, 17, 16},
-      {0, 18, 4},
-      {0, 7, 5},
-      {0, 20, 19},
-      {0, 2, 21},
-      {0, 22, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
-      {0, 0, 0},
-      {35240468, 0, 0},
-      {123060826, 0, 0},
-      {184634770, 0, 0},
-      {359054425, 0, 0},
-      {459968607, 0, 0},
-      {619875033, 0, 0},
-      {904486530, 0, 0},
-      {945128292, 0, 0},
-      {950731750, 0, 0},
-      {1058429216, 0, 0},
-      {1182296898, 0, 0},
-      {1238120570, 0, 0},
-      {1429389803, 0, 0},
-      {1652168174, 0, 0},
-      {1717510093, 0, 0},
-      {1766422419, 0, 0},
-      {1775308984, 0, 0},
-      {1776629361, 0, 0},
-      {1824526196, 0, 0},
-      {1957265068, 0, 0},
-      {1998433745, 0, 0},
-      {2055664760, 0, 0},
-      {2303184249, 0, 0},
-      {2451531615, 0, 0},
-      {2507457870, 0, 0},
-      {2550501832, 0, 0},
-      {2590402790, 0, 0},
-      {2649103430, 0, 0},
-      {2780190687, 0, 0},
-      {2831059514, 0, 0},
-      {3167253437, 0, 0},
-      {3269075805, 0, 0},
-      {3323202731, 0, 0},
-      {3361419439, 0, 0},
-      {3464197236, 0, 0},
-      {3472029049, 0, 0},
-      {3518630848, 0, 0},
-      {3604842236, 0, 0},
-      {3653985133, 0, 0},
-      {4091916710, 0, 0},
-      {4121643374, 0, 0},
-      {4185590212, 0, 0},
-      {4233562270, 0, 0},
-      {4235213885, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 40},
-      {0, 14, 31},
-      {0, 7, 9},
-      {0, 29, 27},
-      {0, 18, 44},
-      {0, 8, 5},
-      {0, 10, 3},
-      {0, 41, 37},
-      {0, 42, 35},
-      {0, 2, 1},
-      {0, 47, 46},
-      {0, 48, 4},
-      {0, 11, 49},
-      {0, 50, 36},
-      {0, 19, 51},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 15, 12},
-      {0, 26, 16},
-      {0, 56, 21},
-      {0, 25, 33},
-      {0, 43, 24},
-      {0, 57, 39},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 62, 34},
-      {0, 64, 63},
-      {0, 17, 30},
-      {0, 66, 65},
-      {0, 20, 67},
-      {0, 13, 68},
-      {0, 28, 69},
-      {0, 70, 32},
-      {0, 72, 71},
-      {0, 73, 22},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 80, 23},
-      {0, 45, 81},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 38, 86},
-      {0, 88, 87},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
-      {0, 0, 0},
-      {75986790, 0, 0},
-      {95470391, 0, 0},
-      {170378107, 0, 0},
-      {172029722, 0, 0},
-      {204234270, 0, 0},
-      {205885885, 0, 0},
-      {244668133, 0, 0},
-      {265778447, 0, 0},
-      {753954113, 0, 0},
-      {1000070091, 0, 0},
-      {1671139745, 0, 0},
-      {1774874546, 0, 0},
-      {1776526161, 0, 0},
-      {1887808856, 0, 0},
-      {1889460471, 0, 0},
-      {1917966999, 0, 0},
-      {2044728014, 0, 0},
-      {2192810893, 0, 0},
-      {2293247016, 0, 0},
-      {2503194620, 0, 0},
-      {2608484640, 0, 0},
-      {2615111110, 0, 0},
-      {2668769415, 0, 0},
-      {2759951687, 0, 0},
-      {2761603302, 0, 0},
-      {2856623532, 0, 0},
-      {2956189845, 0, 0},
-      {3085119011, 0, 0},
-      {3367313400, 0, 0},
-      {3447882276, 0, 0},
-      {3633746133, 0, 0},
-      {3635397748, 0, 0},
-      {3710645347, 0, 0},
-      {3712296962, 0, 0},
-      {3727494858, 0, 0},
-      {3747079365, 0, 0},
-      {3748965853, 0, 0},
-      {3750617468, 0, 0},
-      {4018820793, 0, 0},
-      {4022124023, 0, 0},
-      {4024173916, 0, 0},
-      {4215670524, 0, 0},
-      {4217322139, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 39, 28},
-      {0, 29, 40},
-      {0, 37, 33},
-      {0, 38, 12},
-      {0, 9, 26},
-      {0, 18, 6},
-      {0, 41, 3},
-      {0, 11, 13},
-      {0, 5, 8},
-      {0, 45, 30},
-      {0, 22, 46},
-      {0, 48, 47},
-      {0, 16, 17},
-      {0, 34, 49},
-      {0, 51, 50},
-      {0, 53, 52},
-      {0, 7, 2},
-      {0, 23, 21},
-      {0, 54, 10},
-      {0, 20, 36},
-      {0, 55, 35},
-      {0, 56, 4},
-      {0, 43, 57},
-      {0, 59, 58},
-      {0, 60, 42},
-      {0, 62, 61},
-      {0, 63, 15},
-      {0, 64, 31},
-      {0, 14, 65},
-      {0, 66, 24},
-      {0, 67, 32},
-      {0, 68, 19},
-      {0, 70, 69},
-      {0, 71, 27},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 78, 25},
-      {0, 44, 79},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 1, 84},
-      {0, 86, 85},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunction, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
-      {0, 0, 0},
-      {436416061, 0, 0},
-      {543621065, 0, 0},
-      {679771963, 0, 0},
-      {815757910, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1579585816, 0, 0},
-      {1675764636, 0, 0},
-      {1824016656, 0, 0},
-      {1951208733, 0, 0},
-      {2194691858, 0, 0},
-      {2320303498, 0, 0},
-      {2926633629, 0, 0},
-      {3095831808, 0, 0},
-      {3249265647, 0, 0},
-      {3334207724, 0, 0},
-      {4050155669, 0, 0},
-      {4141567741, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 11},
-      {0, 19, 16},
-      {0, 9, 4},
-      {0, 1, 17},
-      {0, 22, 10},
-      {0, 24, 23},
-      {0, 15, 25},
-      {0, 13, 26},
-      {0, 27, 20},
-      {0, 12, 28},
-      {0, 30, 29},
-      {0, 31, 18},
-      {0, 3, 21},
-      {0, 32, 14},
-      {0, 34, 33},
-      {0, 35, 8},
-      {0, 5, 6},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 40, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionParameter, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
-      {0, 0, 0},
-      {522971108, 0, 0},
-      {615341051, 0, 0},
-      {718301639, 0, 0},
-      {985750227, 0, 0},
-      {1395113939, 0, 0},
-      {1510333659, 0, 0},
-      {1642805350, 0, 0},
-      {1846856260, 0, 0},
-      {1957218950, 0, 0},
-      {1977038330, 0, 0},
-      {1978689945, 0, 0},
-      {1980341560, 0, 0},
-      {2262220987, 0, 0},
-      {2674422363, 0, 0},
-      {3197739982, 0, 0},
-      {3465954368, 0, 0},
-      {3941049054, 0, 0},
-      {3945795573, 0, 0},
-      {4080527786, 0, 0},
-      {4154758669, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 17},
-      {0, 4, 15},
-      {0, 8, 7},
-      {0, 2, 20},
-      {0, 22, 19},
-      {0, 24, 23},
-      {0, 14, 25},
-      {0, 16, 26},
-      {0, 27, 13},
-      {0, 6, 28},
-      {0, 30, 29},
-      {0, 31, 10},
-      {0, 11, 21},
-      {0, 32, 12},
-      {0, 34, 33},
-      {0, 35, 5},
-      {0, 9, 18},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 40, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionParameter, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {545678922, 0, 0},
-      {679771963, 0, 0},
-      {899570100, 0, 0},
-      {929101967, 0, 0},
-      {1100599986, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3056042030, 0, 0},
-      {3334207724, 0, 0},
-      {3357250579, 0, 0},
-      {3705139860, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 3},
-      {0, 10, 13},
-      {0, 4, 15},
-      {0, 16, 11},
-      {0, 17, 1},
-      {0, 14, 12},
-      {0, 19, 18},
-      {0, 21, 20},
-      {0, 22, 8},
-      {0, 7, 6},
-      {0, 23, 9},
-      {0, 25, 24},
-      {0, 26, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(115, {
-      {0, 0, 0},
-      {57149555, 0, 0},
-      {86116519, 0, 0},
-      {168339452, 0, 0},
-      {181902171, 0, 0},
-      {284226441, 0, 0},
-      {314809953, 0, 0},
-      {330249537, 0, 0},
-      {527665290, 0, 0},
-      {545363837, 0, 0},
-      {707478563, 0, 0},
-      {740921498, 0, 0},
-      {807276090, 0, 0},
-      {824323032, 0, 0},
-      {835458563, 0, 0},
-      {1162127370, 0, 0},
-      {1245448751, 0, 0},
-      {1277245109, 0, 0},
-      {1375043498, 0, 0},
-      {1380991098, 0, 0},
-      {1603937321, 0, 0},
-      {1708264968, 0, 0},
-      {1717555224, 0, 0},
-      {1765126703, 0, 0},
-      {1838993983, 0, 0},
-      {1949856502, 0, 0},
-      {2108571893, 0, 0},
-      {2110223508, 0, 0},
-      {2293637521, 0, 0},
-      {2377112119, 0, 0},
-      {2378763734, 0, 0},
-      {2512398201, 0, 0},
-      {2516325050, 0, 0},
-      {2645135839, 0, 0},
-      {2708915136, 0, 0},
-      {2894979602, 0, 0},
-      {2903897222, 0, 0},
-      {2976581453, 0, 0},
-      {3054834317, 0, 0},
-      {3075866530, 0, 0},
-      {3085157904, 0, 0},
-      {3242843022, 0, 0},
-      {3266028549, 0, 0},
-      {3296691317, 0, 0},
-      {3299488628, 0, 0},
-      {3322500634, 0, 0},
-      {3345707173, 0, 0},
-      {3536390697, 0, 0},
-      {3584683259, 0, 0},
-      {3647606635, 0, 0},
-      {3760372982, 0, 0},
-      {3823959661, 0, 0},
-      {3839389658, 0, 0},
-      {4124281183, 0, 0},
-      {4130950286, 0, 0},
-      {4169878842, 0, 0},
-      {4174489262, 0, 0},
-      {4237497041, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 17, 23},
-      {0, 37, 8},
-      {0, 45, 39},
-      {0, 41, 14},
-      {0, 48, 43},
-      {0, 40, 31},
-      {0, 19, 29},
-      {0, 53, 26},
-      {0, 10, 5},
-      {0, 50, 24},
-      {0, 27, 3},
-      {0, 59, 32},
-      {0, 51, 18},
-      {0, 52, 55},
-      {0, 60, 57},
-      {0, 62, 61},
-      {0, 36, 33},
-      {0, 64, 63},
-      {0, 65, 22},
-      {0, 66, 46},
-      {0, 6, 67},
-      {0, 68, 13},
-      {0, 21, 44},
-      {0, 1, 69},
-      {0, 30, 11},
-      {0, 71, 70},
-      {0, 12, 72},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 16, 2},
-      {0, 49, 35},
-      {0, 77, 9},
-      {0, 42, 28},
-      {0, 15, 78},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 47, 83},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 20, 38},
-      {0, 54, 90},
-      {0, 34, 91},
-      {0, 93, 92},
-      {0, 25, 94},
-      {0, 95, 7},
-      {0, 97, 96},
-      {0, 56, 98},
-      {0, 100, 99},
-      {0, 102, 101},
-      {0, 104, 103},
-      {0, 4, 105},
-      {0, 107, 106},
-      {0, 58, 108},
-      {0, 110, 109},
-      {0, 112, 111},
-      {0, 114, 113},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(81, {
-      {0, 0, 0},
-      {35240468, 0, 0},
-      {36096192, 0, 0},
-      {123060826, 0, 0},
-      {184634770, 0, 0},
-      {459968607, 0, 0},
-      {619875033, 0, 0},
-      {950731750, 0, 0},
-      {1058429216, 0, 0},
-      {1182296898, 0, 0},
-      {1238120570, 0, 0},
-      {1271484400, 0, 0},
-      {1429389803, 0, 0},
-      {1717510093, 0, 0},
-      {1766422419, 0, 0},
-      {1775308984, 0, 0},
-      {1817271123, 0, 0},
-      {1917336504, 0, 0},
-      {1957265068, 0, 0},
-      {1998433745, 0, 0},
-      {2055664760, 0, 0},
-      {2303184249, 0, 0},
-      {2308565678, 0, 0},
-      {2451531615, 0, 0},
-      {2496297824, 0, 0},
-      {2507457870, 0, 0},
-      {2550501832, 0, 0},
-      {2590402790, 0, 0},
-      {2649103430, 0, 0},
-      {2831059514, 0, 0},
-      {2836440943, 0, 0},
-      {3269075805, 0, 0},
-      {3361419439, 0, 0},
-      {3457269042, 0, 0},
-      {3464197236, 0, 0},
-      {3472029049, 0, 0},
-      {3518630848, 0, 0},
-      {3587381650, 0, 0},
-      {3653985133, 0, 0},
-      {4185590212, 0, 0},
-      {4233562270, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 40, 37},
-      {0, 22, 30},
-      {0, 2, 7},
-      {0, 24, 11},
-      {0, 16, 33},
-      {0, 6, 34},
-      {0, 42, 27},
-      {0, 5, 43},
-      {0, 4, 44},
-      {0, 36, 8},
-      {0, 39, 45},
-      {0, 46, 1},
-      {0, 3, 47},
-      {0, 48, 23},
-      {0, 49, 9},
-      {0, 50, 35},
-      {0, 52, 51},
-      {0, 32, 53},
-      {0, 13, 10},
-      {0, 26, 14},
-      {0, 19, 54},
-      {0, 55, 25},
-      {0, 56, 38},
-      {0, 17, 57},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 62, 29},
-      {0, 12, 15},
-      {0, 18, 63},
-      {0, 28, 64},
-      {0, 65, 31},
-      {0, 67, 66},
-      {0, 20, 41},
-      {0, 69, 68},
-      {0, 71, 70},
-      {0, 21, 72},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 80, 79},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(61, {
-      {0, 0, 0},
-      {37459569, 0, 0},
-      {162167595, 0, 0},
-      {535067202, 0, 0},
-      {701281393, 0, 0},
-      {837715723, 0, 0},
-      {1320550031, 0, 0},
-      {1630583316, 0, 0},
-      {1913735398, 0, 0},
-      {1918481917, 0, 0},
-      {1955871800, 0, 0},
-      {1977038330, 0, 0},
-      {2053214130, 0, 0},
-      {2443959748, 0, 0},
-      {2564745684, 0, 0},
-      {2622612602, 0, 0},
-      {2677252364, 0, 0},
-      {2736026107, 0, 0},
-      {2790624748, 0, 0},
-      {2882994691, 0, 0},
-      {2888125966, 0, 0},
-      {2970183398, 0, 0},
-      {3253403867, 0, 0},
-      {3427283542, 0, 0},
-      {3570411982, 0, 0},
-      {3619787319, 0, 0},
-      {3662767579, 0, 0},
-      {3884846406, 0, 0},
-      {3910458990, 0, 0},
-      {3927915220, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 20},
-      {0, 6, 25},
-      {0, 23, 3},
-      {0, 2, 4},
-      {0, 14, 17},
-      {0, 11, 8},
-      {0, 27, 10},
-      {0, 19, 28},
-      {0, 12, 16},
-      {0, 33, 32},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 40, 15},
-      {0, 41, 7},
-      {0, 1, 21},
-      {0, 24, 13},
-      {0, 29, 42},
-      {0, 44, 43},
-      {0, 22, 45},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 50, 30},
-      {0, 31, 51},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 56, 9},
-      {0, 57, 26},
-      {0, 59, 58},
-      {0, 60, 18},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(39, {
-      {0, 0, 0},
-      {744062262, 0, 0},
-      {810488476, 0, 0},
-      {1040775722, 0, 0},
-      {1280126114, 0, 0},
-      {1367301635, 0, 0},
-      {1684282922, 0, 0},
-      {1918481917, 0, 0},
-      {1978689945, 0, 0},
-      {1980341560, 0, 0},
-      {2443959748, 0, 0},
-      {2629265310, 0, 0},
-      {2790624748, 0, 0},
-      {2970183398, 0, 0},
-      {3044188332, 0, 0},
-      {3496407048, 0, 0},
-      {3662767579, 0, 0},
-      {3887377256, 0, 0},
-      {3971481069, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 2},
-      {0, 18, 15},
-      {0, 21, 6},
-      {0, 13, 11},
-      {0, 4, 22},
-      {0, 14, 1},
-      {0, 24, 23},
-      {0, 25, 8},
-      {0, 27, 26},
-      {0, 20, 17},
-      {0, 5, 28},
-      {0, 29, 9},
-      {0, 16, 10},
-      {0, 31, 30},
-      {0, 32, 7},
-      {0, 19, 33},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 38, 12},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {37459569, 0, 0},
-      {837715723, 0, 0},
-      {1352628475, 0, 0},
-      {1918481917, 0, 0},
-      {1978689945, 0, 0},
-      {1980341560, 0, 0},
-      {2096388952, 0, 0},
-      {2622612602, 0, 0},
-      {2790624748, 0, 0},
-      {2970183398, 0, 0},
-      {3510682541, 0, 0},
-      {3783543823, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 11},
-      {0, 2, 8},
-      {0, 15, 12},
-      {0, 1, 3},
-      {0, 16, 6},
-      {0, 18, 17},
-      {0, 19, 14},
-      {0, 20, 5},
-      {0, 10, 21},
-      {0, 22, 4},
-      {0, 23, 13},
-      {0, 25, 24},
-      {0, 9, 26},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {1510333659, 0, 0},
-      {1684282922, 0, 0},
-      {1918481917, 0, 0},
-      {2790624748, 0, 0},
-      {3662767579, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 1},
-      {0, 8, 2},
-      {0, 9, 7},
-      {0, 3, 10},
-      {0, 6, 11},
-      {0, 4, 12},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {161668409, 0, 0},
-      {188347929, 0, 0},
-      {653708953, 0, 0},
-      {976111724, 0, 0},
-      {1510333659, 0, 0},
-      {1918481917, 0, 0},
-      {2790624748, 0, 0},
-      {3033873113, 0, 0},
-      {3499234137, 0, 0},
-      {3525913657, 0, 0},
-      {3552593177, 0, 0},
-      {3570411982, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 3},
-      {0, 2, 9},
-      {0, 10, 11},
-      {0, 15, 1},
-      {0, 17, 16},
-      {0, 19, 18},
-      {0, 5, 4},
-      {0, 20, 6},
-      {0, 12, 21},
-      {0, 14, 22},
-      {0, 24, 23},
-      {0, 7, 25},
-      {0, 13, 26},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 7), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(31, {
-      {0, 0, 0},
-      {226836633, 0, 0},
-      {296981500, 0, 0},
-      {718877177, 0, 0},
-      {745556697, 0, 0},
-      {798915737, 0, 0},
-      {1510333659, 0, 0},
-      {1684282922, 0, 0},
-      {2444465148, 0, 0},
-      {2713718873, 0, 0},
-      {3495546641, 0, 0},
-      {3564402361, 0, 0},
-      {4056442905, 0, 0},
-      {4083122425, 0, 0},
-      {4123141705, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 4},
-      {0, 5, 3},
-      {0, 9, 8},
-      {0, 13, 12},
-      {0, 1, 11},
-      {0, 18, 17},
-      {0, 2, 19},
-      {0, 21, 20},
-      {0, 23, 22},
-      {0, 25, 24},
-      {0, 26, 7},
-      {0, 27, 16},
-      {0, 10, 6},
-      {0, 29, 28},
-      {0, 15, 30},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 8), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
-      {0, 0, 0},
-      {161668409, 0, 0},
-      {188347929, 0, 0},
-      {215027449, 0, 0},
-      {296981500, 0, 0},
-      {653708953, 0, 0},
-      {680388473, 0, 0},
-      {1119069977, 0, 0},
-      {1510333659, 0, 0},
-      {1584774136, 0, 0},
-      {2049792025, 0, 0},
-      {2444465148, 0, 0},
-      {2568512089, 0, 0},
-      {3033873113, 0, 0},
-      {3499234137, 0, 0},
-      {3525913657, 0, 0},
-      {3552593177, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 6},
-      {0, 10, 12},
-      {0, 4, 3},
-      {0, 16, 11},
-      {0, 19, 14},
-      {0, 5, 2},
-      {0, 20, 13},
-      {0, 21, 15},
-      {0, 1, 22},
-      {0, 24, 23},
-      {0, 26, 25},
-      {0, 28, 27},
-      {0, 18, 29},
-      {0, 8, 30},
-      {0, 32, 31},
-      {0, 9, 33},
-      {0, 17, 34},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 9), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
-      {0, 0, 0},
-      {825595257, 0, 0},
-      {1064945649, 0, 0},
-      {1290956281, 0, 0},
-      {1510333659, 0, 0},
-      {2096388952, 0, 0},
-      {2248357849, 0, 0},
-      {2713718873, 0, 0},
-      {3187066832, 0, 0},
-      {3205759417, 0, 0},
-      {4064212479, 0, 0},
-      {4163160985, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 3},
-      {0, 2, 9},
-      {0, 7, 6},
-      {0, 5, 14},
-      {0, 16, 15},
-      {0, 17, 11},
-      {0, 19, 18},
-      {0, 20, 1},
-      {0, 4, 13},
-      {0, 22, 21},
-      {0, 10, 23},
-      {0, 12, 24},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 10), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {123108003, 0, 0},
-      {296981500, 0, 0},
-      {595410904, 0, 0},
-      {1466938734, 0, 0},
-      {1503477720, 0, 0},
-      {1816558243, 0, 0},
-      {1990431740, 0, 0},
-      {2724625059, 0, 0},
-      {2790624748, 0, 0},
-      {2812498065, 0, 0},
-      {3160388974, 0, 0},
-      {3745223676, 0, 0},
-      {3982311384, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 13},
-      {0, 8, 1},
-      {0, 12, 11},
-      {0, 15, 3},
-      {0, 6, 4},
-      {0, 16, 7},
-      {0, 17, 14},
-      {0, 18, 2},
-      {0, 19, 10},
-      {0, 21, 20},
-      {0, 23, 22},
-      {0, 25, 24},
-      {0, 9, 26},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 11), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(25, {
-      {0, 0, 0},
-      {94145952, 0, 0},
-      {1054641568, 0, 0},
-      {1269075360, 0, 0},
-      {1675922848, 0, 0},
-      {2038205856, 0, 0},
-      {2433519008, 0, 0},
-      {2636942752, 0, 0},
-      {2790624748, 0, 0},
-      {2840366496, 0, 0},
-      {2851900832, 0, 0},
-      {2964622752, 0, 0},
-      {3654061472, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 1},
-      {0, 12, 6},
-      {0, 14, 10},
-      {0, 13, 4},
-      {0, 11, 15},
-      {0, 3, 16},
-      {0, 2, 17},
-      {0, 18, 5},
-      {0, 9, 19},
-      {0, 21, 20},
-      {0, 23, 22},
-      {0, 8, 24},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 12), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(45, {
-      {0, 0, 0},
-      {107544081, 0, 0},
-      {125015036, 0, 0},
-      {586244865, 0, 0},
-      {1033081852, 0, 0},
-      {1064945649, 0, 0},
-      {1155765244, 0, 0},
-      {1304296041, 0, 0},
-      {1543646433, 0, 0},
-      {1782996825, 0, 0},
-      {1941148668, 0, 0},
-      {2002490364, 0, 0},
-      {2022347217, 0, 0},
-      {2063832060, 0, 0},
-      {2487708241, 0, 0},
-      {2726532092, 0, 0},
-      {2849215484, 0, 0},
-      {2966409025, 0, 0},
-      {3445109809, 0, 0},
-      {3458449569, 0, 0},
-      {3634598908, 0, 0},
-      {3695940604, 0, 0},
-      {3923810593, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 2},
-      {0, 14, 13},
-      {0, 1, 23},
-      {0, 6, 5},
-      {0, 16, 15},
-      {0, 24, 17},
-      {0, 12, 25},
-      {0, 22, 18},
-      {0, 10, 26},
-      {0, 28, 27},
-      {0, 21, 29},
-      {0, 31, 30},
-      {0, 9, 8},
-      {0, 11, 32},
-      {0, 33, 19},
-      {0, 3, 34},
-      {0, 36, 35},
-      {0, 38, 37},
-      {0, 20, 39},
-      {0, 41, 40},
-      {0, 42, 4},
-      {0, 44, 43},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 13), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(23, {
-      {0, 0, 0},
-      {247698428, 0, 0},
-      {309040124, 0, 0},
-      {333554713, 0, 0},
-      {572905105, 0, 0},
-      {1033081852, 0, 0},
-      {2002490364, 0, 0},
-      {2009007457, 0, 0},
-      {2487708241, 0, 0},
-      {3634598908, 0, 0},
-      {3695940604, 0, 0},
-      {3923810593, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 1},
-      {0, 9, 7},
-      {0, 5, 12},
-      {0, 14, 13},
-      {0, 15, 8},
-      {0, 3, 16},
-      {0, 17, 11},
-      {0, 10, 4},
-      {0, 2, 18},
-      {0, 20, 19},
-      {0, 22, 21},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 14), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {247698428, 0, 0},
-      {1033081852, 0, 0},
-      {2002490364, 0, 0},
-      {2910557180, 0, 0},
-      {3757282300, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 4},
-      {0, 7, 3},
-      {0, 2, 8},
-      {0, 1, 5},
-      {0, 10, 9},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 15), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {1033081852, 0, 0},
-      {1094423548, 0, 0},
-      {2002490364, 0, 0},
-      {3757282300, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 6, 2},
-      {0, 4, 7},
-      {0, 8, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFunctionCall, 16), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {135486769, 0, 0},
-      {450406196, 0, 0},
-      {503094540, 0, 0},
-      {543621065, 0, 0},
-      {827698488, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {1322549027, 0, 0},
-      {1784441183, 0, 0},
-      {2194691858, 0, 0},
-      {2448331885, 0, 0},
-      {2468230023, 0, 0},
-      {2547657777, 0, 0},
-      {2549309392, 0, 0},
-      {2550961007, 0, 0},
-      {2934934694, 0, 0},
-      {2936586309, 0, 0},
-      {2938237924, 0, 0},
-      {3094180193, 0, 0},
-      {3095831808, 0, 0},
-      {3183924418, 0, 0},
-      {3561562003, 0, 0},
-      {3563213618, 0, 0},
-      {3564865233, 0, 0},
-      {4028622909, 0, 0},
-      {4039938779, 0, 0},
-      {4050155669, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 27, 28},
-      {0, 10, 2},
-      {0, 25, 24},
-      {0, 1, 12},
-      {0, 30, 3},
-      {0, 20, 31},
-      {0, 9, 32},
-      {0, 34, 33},
-      {0, 35, 22},
-      {0, 26, 15},
-      {0, 19, 36},
-      {0, 18, 37},
-      {0, 38, 16},
-      {0, 39, 8},
-      {0, 5, 40},
-      {0, 6, 41},
-      {0, 21, 42},
-      {0, 11, 29},
-      {0, 4, 43},
-      {0, 13, 23},
-      {0, 14, 17},
-      {0, 7, 44},
-      {0, 46, 45},
-      {0, 48, 47},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(57, {
-      {0, 0, 0},
-      {37459569, 0, 0},
-      {112745085, 0, 0},
-      {137840602, 0, 0},
-      {565334834, 0, 0},
-      {625975427, 0, 0},
-      {630964591, 0, 0},
-      {680016782, 0, 0},
-      {769422756, 0, 0},
-      {1009983433, 0, 0},
-      {1093210099, 0, 0},
-      {1572088444, 0, 0},
-      {1584774136, 0, 0},
-      {1641565587, 0, 0},
-      {1918481917, 0, 0},
-      {2190437442, 0, 0},
-      {2790624748, 0, 0},
-      {3085467405, 0, 0},
-      {3181646225, 0, 0},
-      {3192069648, 0, 0},
-      {3253403867, 0, 0},
-      {3390051757, 0, 0},
-      {3560665067, 0, 0},
-      {3662767579, 0, 0},
-      {4053789056, 0, 0},
-      {4064212479, 0, 0},
-      {4192247221, 0, 0},
-      {4224872590, 0, 0},
-      {4290024976, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 20},
-      {0, 28, 10},
-      {0, 13, 8},
-      {0, 15, 17},
-      {0, 30, 21},
-      {0, 19, 31},
-      {0, 4, 32},
-      {0, 34, 33},
-      {0, 35, 5},
-      {0, 7, 24},
-      {0, 9, 36},
-      {0, 3, 37},
-      {0, 38, 6},
-      {0, 39, 23},
-      {0, 27, 40},
-      {0, 14, 41},
-      {0, 25, 42},
-      {0, 1, 29},
-      {0, 12, 43},
-      {0, 11, 26},
-      {0, 18, 22},
-      {0, 16, 44},
-      {0, 46, 45},
-      {0, 48, 47},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 56, 55},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVariable, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(27, {
-      {0, 0, 0},
-      {162255877, 0, 0},
-      {679771963, 0, 0},
-      {789872778, 0, 0},
-      {1154919607, 0, 0},
-      {1343794461, 0, 0},
-      {1951208733, 0, 0},
-      {2263349224, 0, 0},
-      {2320303498, 0, 0},
-      {2924146124, 0, 0},
-      {2984325996, 0, 0},
-      {3334207724, 0, 0},
-      {3868239231, 0, 0},
-      {3869890846, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 5, 3},
-      {0, 9, 7},
-      {0, 12, 4},
-      {0, 16, 15},
-      {0, 18, 17},
-      {0, 14, 19},
-      {0, 13, 10},
-      {0, 20, 1},
-      {0, 21, 8},
-      {0, 2, 22},
-      {0, 11, 23},
-      {0, 6, 24},
-      {0, 26, 25},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
-      {0, 0, 0},
-      {169674806, 0, 0},
-      {269823086, 0, 0},
-      {408465899, 0, 0},
-      {451264926, 0, 0},
-      {543558236, 0, 0},
-      {810488476, 0, 0},
-      {850497536, 0, 0},
-      {870594305, 0, 0},
-      {883854656, 0, 0},
-      {1033363654, 0, 0},
-      {1069781886, 0, 0},
-      {1141965917, 0, 0},
-      {1323407757, 0, 0},
-      {1570165302, 0, 0},
-      {1684282922, 0, 0},
-      {1742737136, 0, 0},
-      {1901166356, 0, 0},
-      {1949759310, 0, 0},
-      {2043873558, 0, 0},
-      {2087004702, 0, 0},
-      {2096388952, 0, 0},
-      {2157103435, 0, 0},
-      {2219733501, 0, 0},
-      {2356768706, 0, 0},
-      {2443959748, 0, 0},
-      {2517964682, 0, 0},
-      {2614879967, 0, 0},
-      {2622612602, 0, 0},
-      {2660843182, 0, 0},
-      {2959147533, 0, 0},
-      {2970183398, 0, 0},
-      {3044188332, 0, 0},
-      {3091876332, 0, 0},
-      {3187066832, 0, 0},
-      {3244209297, 0, 0},
-      {3487022798, 0, 0},
-      {3496407048, 0, 0},
-      {3570411982, 0, 0},
-      {3692647551, 0, 0},
-      {3713290482, 0, 0},
-      {3831290364, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 1},
-      {0, 35, 13},
-      {0, 25, 11},
-      {0, 7, 10},
-      {0, 19, 36},
-      {0, 43, 27},
-      {0, 16, 29},
-      {0, 22, 3},
-      {0, 41, 30},
-      {0, 44, 12},
-      {0, 2, 24},
-      {0, 40, 32},
-      {0, 23, 45},
-      {0, 46, 39},
-      {0, 17, 33},
-      {0, 48, 47},
-      {0, 8, 49},
-      {0, 51, 50},
-      {0, 52, 20},
-      {0, 53, 14},
-      {0, 31, 54},
-      {0, 15, 55},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 6, 26},
-      {0, 61, 60},
-      {0, 34, 62},
-      {0, 64, 63},
-      {0, 5, 37},
-      {0, 9, 65},
-      {0, 18, 28},
-      {0, 66, 38},
-      {0, 68, 67},
-      {0, 69, 21},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 80, 42},
-      {0, 82, 81},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(83, {
-      {0, 0, 0},
-      {28782128, 0, 0},
-      {30433743, 0, 0},
-      {37459569, 0, 0},
-      {137840602, 0, 0},
-      {522971108, 0, 0},
-      {565334834, 0, 0},
-      {625975427, 0, 0},
-      {630964591, 0, 0},
-      {680016782, 0, 0},
-      {1009983433, 0, 0},
-      {1079999262, 0, 0},
-      {1395113939, 0, 0},
-      {1572088444, 0, 0},
-      {1584774136, 0, 0},
-      {1649426421, 0, 0},
-      {1918481917, 0, 0},
-      {1957218950, 0, 0},
-      {2311941439, 0, 0},
-      {2313593054, 0, 0},
-      {2790624748, 0, 0},
-      {2838165089, 0, 0},
-      {2839816704, 0, 0},
-      {2841468319, 0, 0},
-      {3085467405, 0, 0},
-      {3181646225, 0, 0},
-      {3192069648, 0, 0},
-      {3253403867, 0, 0},
-      {3364388739, 0, 0},
-      {3366040354, 0, 0},
-      {3367691969, 0, 0},
-      {3369343584, 0, 0},
-      {3560665067, 0, 0},
-      {3662767579, 0, 0},
-      {3945795573, 0, 0},
-      {4053789056, 0, 0},
-      {4064212479, 0, 0},
-      {4224872590, 0, 0},
-      {4239834800, 0, 0},
-      {4241486415, 0, 0},
-      {4243138030, 0, 0},
-      {4244789645, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 27},
-      {0, 15, 2},
-      {0, 10, 26},
-      {0, 7, 24},
-      {0, 9, 31},
-      {0, 43, 30},
-      {0, 29, 12},
-      {0, 11, 41},
-      {0, 40, 39},
-      {0, 44, 23},
-      {0, 22, 6},
-      {0, 34, 35},
-      {0, 18, 45},
-      {0, 46, 21},
-      {0, 17, 19},
-      {0, 48, 47},
-      {0, 28, 49},
-      {0, 51, 50},
-      {0, 52, 38},
-      {0, 53, 33},
-      {0, 4, 54},
-      {0, 13, 55},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 37, 8},
-      {0, 61, 60},
-      {0, 5, 62},
-      {0, 64, 63},
-      {0, 36, 32},
-      {0, 3, 65},
-      {0, 14, 16},
-      {0, 66, 25},
-      {0, 68, 67},
-      {0, 69, 20},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 80, 42},
-      {0, 82, 81},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLoad, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
-      {0, 0, 0},
-      {137840602, 0, 0},
-      {522971108, 0, 0},
-      {769422756, 0, 0},
-      {1009983433, 0, 0},
-      {1079999262, 0, 0},
-      {1558345254, 0, 0},
-      {1572088444, 0, 0},
-      {1641565587, 0, 0},
-      {1918481917, 0, 0},
-      {2311941439, 0, 0},
-      {2313593054, 0, 0},
-      {2790624748, 0, 0},
-      {2838165089, 0, 0},
-      {2994529201, 0, 0},
-      {2996180816, 0, 0},
-      {2997832431, 0, 0},
-      {3027538652, 0, 0},
-      {3253403867, 0, 0},
-      {3364388739, 0, 0},
-      {3560665067, 0, 0},
-      {3662767579, 0, 0},
-      {3945795573, 0, 0},
-      {4192247221, 0, 0},
-      {4224872590, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 17},
-      {0, 16, 15},
-      {0, 13, 11},
-      {0, 10, 3},
-      {0, 22, 18},
-      {0, 6, 8},
-      {0, 19, 2},
-      {0, 27, 26},
-      {0, 28, 5},
-      {0, 30, 29},
-      {0, 32, 31},
-      {0, 34, 33},
-      {0, 4, 35},
-      {0, 37, 36},
-      {0, 21, 1},
-      {0, 39, 38},
-      {0, 40, 24},
-      {0, 7, 23},
-      {0, 20, 9},
-      {0, 42, 41},
-      {0, 43, 25},
-      {0, 44, 12},
-      {0, 46, 45},
-      {0, 48, 47},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpStore, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
-      {0, 0, 0},
-      {139011596, 0, 0},
-      {177111659, 0, 0},
-      {296981500, 0, 0},
-      {408465899, 0, 0},
-      {495107308, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {1367301635, 0, 0},
-      {1901166356, 0, 0},
-      {2055836767, 0, 0},
-      {2087004702, 0, 0},
-      {2096388952, 0, 0},
-      {2204920111, 0, 0},
-      {2517964682, 0, 0},
-      {2622612602, 0, 0},
-      {2660843182, 0, 0},
-      {2842919847, 0, 0},
-      {2855506940, 0, 0},
-      {2959147533, 0, 0},
-      {3044188332, 0, 0},
-      {3187066832, 0, 0},
-      {3504158761, 0, 0},
-      {3570411982, 0, 0},
-      {3619787319, 0, 0},
-      {3653838348, 0, 0},
-      {3692647551, 0, 0},
-      {3764205609, 0, 0},
-      {3831290364, 0, 0},
-      {3913885196, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 20, 29},
-      {0, 25, 8},
-      {0, 5, 1},
-      {0, 24, 26},
-      {0, 14, 9},
-      {0, 27, 16},
-      {0, 31, 7},
-      {0, 33, 32},
-      {0, 17, 34},
-      {0, 35, 13},
-      {0, 22, 6},
-      {0, 3, 2},
-      {0, 23, 36},
-      {0, 28, 37},
-      {0, 19, 4},
-      {0, 38, 10},
-      {0, 39, 15},
-      {0, 40, 18},
-      {0, 42, 41},
-      {0, 43, 12},
-      {0, 44, 21},
-      {0, 45, 11},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 57, 56},
-      {0, 30, 58},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpStore, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(35, {
-      {0, 0, 0},
-      {440421571, 0, 0},
-      {827698488, 0, 0},
-      {907126242, 0, 0},
-      {908777857, 0, 0},
-      {910429472, 0, 0},
-      {1294403159, 0, 0},
-      {1296054774, 0, 0},
-      {1297706389, 0, 0},
-      {2080953106, 0, 0},
-      {2468230023, 0, 0},
-      {2547657777, 0, 0},
-      {2549309392, 0, 0},
-      {2550961007, 0, 0},
-      {3094857332, 0, 0},
-      {3561562003, 0, 0},
-      {3563213618, 0, 0},
-      {3564865233, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 16, 12},
-      {0, 17, 13},
-      {0, 14, 19},
-      {0, 18, 20},
-      {0, 5, 21},
-      {0, 11, 7},
-      {0, 15, 22},
-      {0, 9, 8},
-      {0, 24, 23},
-      {0, 25, 4},
-      {0, 27, 26},
-      {0, 28, 3},
-      {0, 29, 10},
-      {0, 6, 1},
-      {0, 31, 30},
-      {0, 32, 2},
-      {0, 34, 33},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(99, {
-      {0, 0, 0},
-      {27130513, 0, 0},
-      {28782128, 0, 0},
-      {30433743, 0, 0},
-      {32085358, 0, 0},
-      {155458798, 0, 0},
-      {157110413, 0, 0},
-      {163402553, 0, 0},
-      {165054168, 0, 0},
-      {213642219, 0, 0},
-      {215293834, 0, 0},
-      {216945449, 0, 0},
-      {221900294, 0, 0},
-      {545986953, 0, 0},
-      {979993429, 0, 0},
-      {1079999262, 0, 0},
-      {1302400505, 0, 0},
-      {1313182965, 0, 0},
-      {1314834580, 0, 0},
-      {1315613425, 0, 0},
-      {1317265040, 0, 0},
-      {1558345254, 0, 0},
-      {1649426421, 0, 0},
-      {2311941439, 0, 0},
-      {2313593054, 0, 0},
-      {2602027658, 0, 0},
-      {2838165089, 0, 0},
-      {2839816704, 0, 0},
-      {2841468319, 0, 0},
-      {2863084840, 0, 0},
-      {2994529201, 0, 0},
-      {2996180816, 0, 0},
-      {2997832431, 0, 0},
-      {3027538652, 0, 0},
-      {3187387500, 0, 0},
-      {3189039115, 0, 0},
-      {3364388739, 0, 0},
-      {3366040354, 0, 0},
-      {3367691969, 0, 0},
-      {3369343584, 0, 0},
-      {3716914380, 0, 0},
-      {3928842969, 0, 0},
-      {3930494584, 0, 0},
-      {3932146199, 0, 0},
-      {3945482286, 0, 0},
-      {4105051793, 0, 0},
-      {4239834800, 0, 0},
-      {4241486415, 0, 0},
-      {4243138030, 0, 0},
-      {4244789645, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 29, 10},
-      {0, 17, 18},
-      {0, 13, 14},
-      {0, 44, 25},
-      {0, 8, 7},
-      {0, 20, 11},
-      {0, 33, 19},
-      {0, 6, 45},
-      {0, 42, 43},
-      {0, 40, 5},
-      {0, 9, 16},
-      {0, 1, 4},
-      {0, 35, 34},
-      {0, 12, 21},
-      {0, 52, 51},
-      {0, 31, 30},
-      {0, 41, 32},
-      {0, 54, 53},
-      {0, 55, 2},
-      {0, 3, 56},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 61, 22},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 39, 68},
-      {0, 38, 69},
-      {0, 47, 70},
-      {0, 49, 71},
-      {0, 28, 48},
-      {0, 37, 15},
-      {0, 73, 72},
-      {0, 74, 27},
-      {0, 23, 75},
-      {0, 76, 26},
-      {0, 24, 77},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 82, 46},
-      {0, 36, 83},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 50, 98},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(101, {
-      {0, 0, 0},
-      {112745085, 0, 0},
-      {116376005, 0, 0},
-      {137840602, 0, 0},
-      {400248103, 0, 0},
-      {406044930, 0, 0},
-      {468372467, 0, 0},
-      {522971108, 0, 0},
-      {615341051, 0, 0},
-      {625975427, 0, 0},
-      {630964591, 0, 0},
-      {680016782, 0, 0},
-      {763027711, 0, 0},
-      {977312655, 0, 0},
-      {1009983433, 0, 0},
-      {1062250709, 0, 0},
-      {1395113939, 0, 0},
-      {1410849099, 0, 0},
-      {1642805350, 0, 0},
-      {1692932387, 0, 0},
-      {1698730948, 0, 0},
-      {1827244161, 0, 0},
-      {1918481917, 0, 0},
-      {2096472894, 0, 0},
-      {2190437442, 0, 0},
-      {2299842241, 0, 0},
-      {2433358586, 0, 0},
-      {2593325766, 0, 0},
-      {2785441472, 0, 0},
-      {2790624748, 0, 0},
-      {2879917723, 0, 0},
-      {2882994691, 0, 0},
-      {2902069960, 0, 0},
-      {3090408469, 0, 0},
-      {3181646225, 0, 0},
-      {3255947500, 0, 0},
-      {3263901372, 0, 0},
-      {3268751013, 0, 0},
-      {3347863687, 0, 0},
-      {3390051757, 0, 0},
-      {3560665067, 0, 0},
-      {3617689692, 0, 0},
-      {3662767579, 0, 0},
-      {3717523241, 0, 0},
-      {3854557817, 0, 0},
-      {3910458990, 0, 0},
-      {3941049054, 0, 0},
-      {3945795573, 0, 0},
-      {4080527786, 0, 0},
-      {4101009465, 0, 0},
-      {4290024976, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 32, 44},
-      {0, 41, 26},
-      {0, 16, 10},
-      {0, 27, 45},
-      {0, 25, 38},
-      {0, 12, 18},
-      {0, 6, 35},
-      {0, 46, 23},
-      {0, 20, 37},
-      {0, 52, 19},
-      {0, 53, 21},
-      {0, 54, 48},
-      {0, 33, 55},
-      {0, 3, 8},
-      {0, 28, 56},
-      {0, 13, 57},
-      {0, 59, 58},
-      {0, 1, 49},
-      {0, 47, 60},
-      {0, 61, 14},
-      {0, 63, 62},
-      {0, 64, 43},
-      {0, 7, 4},
-      {0, 65, 15},
-      {0, 67, 66},
-      {0, 68, 17},
-      {0, 36, 2},
-      {0, 30, 69},
-      {0, 71, 70},
-      {0, 34, 5},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 24, 78},
-      {0, 39, 31},
-      {0, 80, 79},
-      {0, 9, 11},
-      {0, 42, 81},
-      {0, 83, 82},
-      {0, 29, 50},
-      {0, 84, 51},
-      {0, 86, 85},
-      {0, 22, 40},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 100, 99},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(69, {
-      {0, 0, 0},
-      {51041423, 0, 0},
-      {142465290, 0, 0},
-      {144116905, 0, 0},
-      {290391815, 0, 0},
-      {438318340, 0, 0},
-      {529742207, 0, 0},
-      {677668732, 0, 0},
-      {917019124, 0, 0},
-      {1064945649, 0, 0},
-      {1156369516, 0, 0},
-      {1158021131, 0, 0},
-      {1304296041, 0, 0},
-      {1452222566, 0, 0},
-      {1543646433, 0, 0},
-      {1691572958, 0, 0},
-      {1782996825, 0, 0},
-      {1784648440, 0, 0},
-      {1930923350, 0, 0},
-      {2170273742, 0, 0},
-      {2318200267, 0, 0},
-      {2466126792, 0, 0},
-      {2557550659, 0, 0},
-      {2705477184, 0, 0},
-      {2796901051, 0, 0},
-      {2798552666, 0, 0},
-      {2944827576, 0, 0},
-      {3092754101, 0, 0},
-      {3184177968, 0, 0},
-      {3332104493, 0, 0},
-      {3571454885, 0, 0},
-      {3810805277, 0, 0},
-      {3958731802, 0, 0},
-      {4106658327, 0, 0},
-      {4198082194, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 27, 33},
-      {0, 21, 5},
-      {0, 26, 13},
-      {0, 20, 8},
-      {0, 15, 7},
-      {0, 37, 36},
-      {0, 32, 29},
-      {0, 38, 4},
-      {0, 30, 1},
-      {0, 9, 12},
-      {0, 39, 18},
-      {0, 22, 40},
-      {0, 42, 41},
-      {0, 44, 43},
-      {0, 45, 35},
-      {0, 46, 34},
-      {0, 6, 14},
-      {0, 28, 23},
-      {0, 48, 47},
-      {0, 49, 31},
-      {0, 51, 50},
-      {0, 19, 24},
-      {0, 52, 10},
-      {0, 2, 53},
-      {0, 55, 54},
-      {0, 25, 56},
-      {0, 11, 57},
-      {0, 59, 58},
-      {0, 3, 17},
-      {0, 61, 60},
-      {0, 16, 62},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 68, 67},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(85, {
-      {0, 0, 0},
-      {142465290, 0, 0},
-      {144116905, 0, 0},
-      {198967948, 0, 0},
-      {290391815, 0, 0},
-      {529742207, 0, 0},
-      {586244865, 0, 0},
-      {677668732, 0, 0},
-      {825595257, 0, 0},
-      {917019124, 0, 0},
-      {973521782, 0, 0},
-      {1064945649, 0, 0},
-      {1156369516, 0, 0},
-      {1158021131, 0, 0},
-      {1212872174, 0, 0},
-      {1304296041, 0, 0},
-      {1452222566, 0, 0},
-      {1543646433, 0, 0},
-      {1600149091, 0, 0},
-      {1782996825, 0, 0},
-      {1784648440, 0, 0},
-      {1839499483, 0, 0},
-      {1930923350, 0, 0},
-      {2170273742, 0, 0},
-      {2226776400, 0, 0},
-      {2318200267, 0, 0},
-      {2466126792, 0, 0},
-      {2557550659, 0, 0},
-      {2614053317, 0, 0},
-      {2796901051, 0, 0},
-      {2798552666, 0, 0},
-      {2853403709, 0, 0},
-      {2944827576, 0, 0},
-      {3184177968, 0, 0},
-      {3240680626, 0, 0},
-      {3480031018, 0, 0},
-      {3571454885, 0, 0},
-      {3810805277, 0, 0},
-      {3867307935, 0, 0},
-      {3958731802, 0, 0},
-      {4106658327, 0, 0},
-      {4198082194, 0, 0},
-      {4254584852, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 7, 11},
-      {0, 15, 4},
-      {0, 32, 25},
-      {0, 44, 39},
-      {0, 36, 22},
-      {0, 45, 17},
-      {0, 24, 46},
-      {0, 10, 9},
-      {0, 6, 27},
-      {0, 28, 18},
-      {0, 42, 34},
-      {0, 31, 14},
-      {0, 41, 38},
-      {0, 26, 3},
-      {0, 47, 33},
-      {0, 21, 8},
-      {0, 5, 35},
-      {0, 40, 16},
-      {0, 37, 23},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 68, 12},
-      {0, 29, 69},
-      {0, 70, 1},
-      {0, 30, 2},
-      {0, 43, 71},
-      {0, 73, 72},
-      {0, 74, 20},
-      {0, 75, 19},
-      {0, 77, 76},
-      {0, 13, 78},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 84, 83},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {144116905, 0, 0},
-      {1158021131, 0, 0},
-      {1784648440, 0, 0},
-      {2798552666, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 5},
-      {0, 4, 2},
-      {0, 6, 3},
-      {0, 8, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(5, {
-      {0, 0, 0},
-      {142465290, 0, 0},
-      {1782996825, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 1},
-      {0, 4, 3},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpAccessChain, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 2, 5},
-      {0, 6, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
-      {0, 0, 0},
-      {177111659, 0, 0},
-      {413918748, 0, 0},
-      {529383565, 0, 0},
-      {646282397, 0, 0},
-      {837715723, 0, 0},
-      {1019457583, 0, 0},
-      {1022544883, 0, 0},
-      {1054461787, 0, 0},
-      {1097775533, 0, 0},
-      {1136775085, 0, 0},
-      {1191015885, 0, 0},
-      {1196280518, 0, 0},
-      {1203545131, 0, 0},
-      {1352628475, 0, 0},
-      {1367301635, 0, 0},
-      {1918742169, 0, 0},
-      {1922045399, 0, 0},
-      {2055836767, 0, 0},
-      {2183547611, 0, 0},
-      {2204920111, 0, 0},
-      {2358141757, 0, 0},
-      {2572638469, 0, 0},
-      {2597020383, 0, 0},
-      {2842919847, 0, 0},
-      {3619787319, 0, 0},
-      {3701632935, 0, 0},
-      {3783543823, 0, 0},
-      {4245257809, 0, 0},
-      {4265894873, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 11, 23},
-      {0, 12, 2},
-      {0, 9, 7},
-      {0, 21, 19},
-      {0, 4, 29},
-      {0, 10, 28},
-      {0, 17, 16},
-      {0, 27, 3},
-      {0, 32, 31},
-      {0, 33, 22},
-      {0, 6, 34},
-      {0, 35, 8},
-      {0, 36, 24},
-      {0, 38, 37},
-      {0, 1, 14},
-      {0, 39, 20},
-      {0, 5, 40},
-      {0, 42, 41},
-      {0, 43, 26},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 48, 18},
-      {0, 15, 49},
-      {0, 50, 25},
-      {0, 51, 13},
-      {0, 53, 52},
-      {0, 55, 54},
-      {0, 57, 56},
-      {0, 30, 58},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(59, {
-      {0, 0, 0},
-      {236660303, 0, 0},
-      {342159236, 0, 0},
-      {371428004, 0, 0},
-      {373079619, 0, 0},
-      {488500848, 0, 0},
-      {495107308, 0, 0},
-      {864295921, 0, 0},
-      {1071164424, 0, 0},
-      {1136911283, 0, 0},
-      {1178317551, 0, 0},
-      {1510422521, 0, 0},
-      {1570165302, 0, 0},
-      {1822823090, 0, 0},
-      {1858116930, 0, 0},
-      {1977038330, 0, 0},
-      {2096388952, 0, 0},
-      {2157103435, 0, 0},
-      {2231688008, 0, 0},
-      {2604576561, 0, 0},
-      {2622612602, 0, 0},
-      {2771938750, 0, 0},
-      {2777172031, 0, 0},
-      {2996594997, 0, 0},
-      {3187066832, 0, 0},
-      {3496407048, 0, 0},
-      {3570411982, 0, 0},
-      {3609540589, 0, 0},
-      {3713290482, 0, 0},
-      {3797761273, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 18, 8},
-      {0, 27, 9},
-      {0, 21, 10},
-      {0, 14, 24},
-      {0, 12, 19},
-      {0, 11, 15},
-      {0, 23, 2},
-      {0, 7, 13},
-      {0, 31, 22},
-      {0, 32, 4},
-      {0, 33, 29},
-      {0, 34, 1},
-      {0, 35, 3},
-      {0, 37, 36},
-      {0, 38, 28},
-      {0, 39, 5},
-      {0, 41, 40},
-      {0, 42, 17},
-      {0, 16, 43},
-      {0, 45, 44},
-      {0, 46, 6},
-      {0, 48, 47},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 25, 53},
-      {0, 54, 20},
-      {0, 55, 26},
-      {0, 57, 56},
-      {0, 30, 58},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
-      {0, 0, 0},
-      {236660303, 0, 0},
-      {342159236, 0, 0},
-      {488500848, 0, 0},
-      {495107308, 0, 0},
-      {864295921, 0, 0},
-      {1178317551, 0, 0},
-      {1510422521, 0, 0},
-      {1570165302, 0, 0},
-      {1858116930, 0, 0},
-      {1977038330, 0, 0},
-      {2096388952, 0, 0},
-      {2157103435, 0, 0},
-      {2231688008, 0, 0},
-      {2604576561, 0, 0},
-      {2622612602, 0, 0},
-      {2771938750, 0, 0},
-      {2777172031, 0, 0},
-      {2996594997, 0, 0},
-      {3496407048, 0, 0},
-      {3570411982, 0, 0},
-      {3609540589, 0, 0},
-      {3713290482, 0, 0},
-      {3797761273, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 21, 13},
-      {0, 16, 6},
-      {0, 14, 9},
-      {0, 7, 10},
-      {0, 18, 2},
-      {0, 17, 5},
-      {0, 25, 8},
-      {0, 22, 12},
-      {0, 26, 23},
-      {0, 27, 1},
-      {0, 28, 3},
-      {0, 30, 29},
-      {0, 32, 31},
-      {0, 34, 33},
-      {0, 35, 11},
-      {0, 36, 4},
-      {0, 38, 37},
-      {0, 40, 39},
-      {0, 41, 15},
-      {0, 42, 19},
-      {0, 20, 43},
-      {0, 45, 44},
-      {0, 24, 46},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorShuffle, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1146476634, 0, 0},
-      {2160380860, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 3},
-      {0, 9, 6},
-      {0, 8, 7},
-      {0, 11, 10},
-      {0, 4, 12},
-      {0, 5, 13},
-      {0, 14, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(79, {
-      {0, 0, 0},
-      {107497541, 0, 0},
-      {289648234, 0, 0},
-      {348584153, 0, 0},
-      {369686787, 0, 0},
-      {429277936, 0, 0},
-      {449954059, 0, 0},
-      {508217552, 0, 0},
-      {742917749, 0, 0},
-      {1032593647, 0, 0},
-      {1158929937, 0, 0},
-      {1209418480, 0, 0},
-      {1319785741, 0, 0},
-      {1321616112, 0, 0},
-      {1417363940, 0, 0},
-      {1541020250, 0, 0},
-      {1564342316, 0, 0},
-      {1578775276, 0, 0},
-      {1631434666, 0, 0},
-      {1636389511, 0, 0},
-      {2012838864, 0, 0},
-      {2262137600, 0, 0},
-      {2281956980, 0, 0},
-      {2359973133, 0, 0},
-      {2464905186, 0, 0},
-      {2613179511, 0, 0},
-      {2621255555, 0, 0},
-      {2817335337, 0, 0},
-      {2881302403, 0, 0},
-      {3063300848, 0, 0},
-      {3151638847, 0, 0},
-      {3233393284, 0, 0},
-      {3323682385, 0, 0},
-      {3337532056, 0, 0},
-      {3456899824, 0, 0},
-      {3547456240, 0, 0},
-      {3675926744, 0, 0},
-      {3753486980, 0, 0},
-      {3931641900, 0, 0},
-      {3970432934, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 25, 1},
-      {0, 6, 4},
-      {0, 8, 19},
-      {0, 39, 24},
-      {0, 3, 2},
-      {0, 34, 14},
-      {0, 10, 9},
-      {0, 18, 38},
-      {0, 32, 15},
-      {0, 27, 16},
-      {0, 28, 35},
-      {0, 13, 26},
-      {0, 20, 23},
-      {0, 21, 11},
-      {0, 36, 33},
-      {0, 5, 22},
-      {0, 42, 41},
-      {0, 43, 29},
-      {0, 45, 44},
-      {0, 7, 46},
-      {0, 48, 47},
-      {0, 30, 31},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 55, 17},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 62, 12},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 67, 37},
-      {0, 69, 68},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 40, 78},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
-      {0, 0, 0},
-      {153013225, 0, 0},
-      {296836635, 0, 0},
-      {296981500, 0, 0},
-      {778500192, 0, 0},
-      {810488476, 0, 0},
-      {848380423, 0, 0},
-      {900522183, 0, 0},
-      {910398460, 0, 0},
-      {959681532, 0, 0},
-      {1141965917, 0, 0},
-      {1287304304, 0, 0},
-      {1323407757, 0, 0},
-      {1417363940, 0, 0},
-      {1471851763, 0, 0},
-      {1526654696, 0, 0},
-      {1654776395, 0, 0},
-      {1684282922, 0, 0},
-      {1739837626, 0, 0},
-      {1791352211, 0, 0},
-      {2195550588, 0, 0},
-      {2319227476, 0, 0},
-      {2491124112, 0, 0},
-      {2789375411, 0, 0},
-      {2807448986, 0, 0},
-      {2817579280, 0, 0},
-      {2835131395, 0, 0},
-      {2847102741, 0, 0},
-      {2855506940, 0, 0},
-      {2860348412, 0, 0},
-      {3079287749, 0, 0},
-      {3091876332, 0, 0},
-      {3168953855, 0, 0},
-      {3374978006, 0, 0},
-      {3399062057, 0, 0},
-      {3510257966, 0, 0},
-      {3554463148, 0, 0},
-      {3579593979, 0, 0},
-      {3757851979, 0, 0},
-      {3759503594, 0, 0},
-      {3761155209, 0, 0},
-      {3762806824, 0, 0},
-      {3902853271, 0, 0},
-      {4140081844, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 38, 42},
-      {0, 14, 23},
-      {0, 26, 18},
-      {0, 39, 35},
-      {0, 6, 40},
-      {0, 16, 13},
-      {0, 33, 34},
-      {0, 12, 4},
-      {0, 27, 41},
-      {0, 25, 21},
-      {0, 24, 1},
-      {0, 37, 19},
-      {0, 32, 22},
-      {0, 2, 8},
-      {0, 20, 17},
-      {0, 43, 36},
-      {0, 29, 15},
-      {0, 46, 45},
-      {0, 48, 47},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 7, 55},
-      {0, 56, 30},
-      {0, 57, 5},
-      {0, 59, 58},
-      {0, 60, 11},
-      {0, 9, 61},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 66, 31},
-      {0, 68, 67},
-      {0, 10, 69},
-      {0, 71, 70},
-      {0, 28, 72},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 79, 3},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 44, 86},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(81, {
-      {0, 0, 0},
-      {14244860, 0, 0},
-      {150820676, 0, 0},
-      {153013225, 0, 0},
-      {269823086, 0, 0},
-      {289648234, 0, 0},
-      {296981500, 0, 0},
-      {678695941, 0, 0},
-      {810488476, 0, 0},
-      {850592577, 0, 0},
-      {870594305, 0, 0},
-      {910398460, 0, 0},
-      {959681532, 0, 0},
-      {1206571206, 0, 0},
-      {1287304304, 0, 0},
-      {1323407757, 0, 0},
-      {1471851763, 0, 0},
-      {1526654696, 0, 0},
-      {1684282922, 0, 0},
-      {1734446471, 0, 0},
-      {1758530522, 0, 0},
-      {2117320444, 0, 0},
-      {2118972059, 0, 0},
-      {2120623674, 0, 0},
-      {2122275289, 0, 0},
-      {2219733501, 0, 0},
-      {2262321736, 0, 0},
-      {2807448986, 0, 0},
-      {2817579280, 0, 0},
-      {2835131395, 0, 0},
-      {2855506940, 0, 0},
-      {2860348412, 0, 0},
-      {2951272396, 0, 0},
-      {3079287749, 0, 0},
-      {3168953855, 0, 0},
-      {3502816184, 0, 0},
-      {3510257966, 0, 0},
-      {3554463148, 0, 0},
-      {3997952447, 0, 0},
-      {4140081844, 0, 0},
-      {4182141402, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 21, 26},
-      {0, 29, 16},
-      {0, 22, 36},
-      {0, 1, 23},
-      {0, 20, 5},
-      {0, 19, 35},
-      {0, 10, 38},
-      {0, 13, 24},
-      {0, 28, 7},
-      {0, 27, 3},
-      {0, 40, 2},
-      {0, 34, 9},
-      {0, 32, 11},
-      {0, 33, 18},
-      {0, 39, 37},
-      {0, 31, 17},
-      {0, 43, 42},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 49, 48},
-      {0, 51, 50},
-      {0, 8, 52},
-      {0, 15, 53},
-      {0, 55, 54},
-      {0, 56, 14},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 61, 25},
-      {0, 63, 62},
-      {0, 4, 64},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 71, 12},
-      {0, 6, 72},
-      {0, 30, 73},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 41, 80},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(111, {
-      {0, 0, 0},
-      {34183582, 0, 0},
-      {93914936, 0, 0},
-      {94303122, 0, 0},
-      {117998987, 0, 0},
-      {153013225, 0, 0},
-      {296981500, 0, 0},
-      {451264926, 0, 0},
-      {473485679, 0, 0},
-      {476788909, 0, 0},
-      {478440524, 0, 0},
-      {480092139, 0, 0},
-      {481743754, 0, 0},
-      {810488476, 0, 0},
-      {871966503, 0, 0},
-      {910398460, 0, 0},
-      {918189168, 0, 0},
-      {933769938, 0, 0},
-      {959681532, 0, 0},
-      {1149665466, 0, 0},
-      {1166917451, 0, 0},
-      {1227221002, 0, 0},
-      {1310740861, 0, 0},
-      {1323407757, 0, 0},
-      {1341516288, 0, 0},
-      {1373166395, 0, 0},
-      {1445161581, 0, 0},
-      {1461645203, 0, 0},
-      {1471851763, 0, 0},
-      {1526654696, 0, 0},
-      {1561718045, 0, 0},
-      {1593584949, 0, 0},
-      {1684282922, 0, 0},
-      {1800404122, 0, 0},
-      {1862284649, 0, 0},
-      {2213411495, 0, 0},
-      {2668680621, 0, 0},
-      {2805256437, 0, 0},
-      {2807448986, 0, 0},
-      {2835131395, 0, 0},
-      {2855506940, 0, 0},
-      {2860348412, 0, 0},
-      {3000904950, 0, 0},
-      {3107413701, 0, 0},
-      {3168953855, 0, 0},
-      {3333131702, 0, 0},
-      {3365041621, 0, 0},
-      {3456899824, 0, 0},
-      {3505028338, 0, 0},
-      {3510257966, 0, 0},
-      {3554463148, 0, 0},
-      {3606320646, 0, 0},
-      {3692647551, 0, 0},
-      {3861006967, 0, 0},
-      {4126287524, 0, 0},
-      {4140081844, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 33},
-      {0, 35, 25},
-      {0, 27, 17},
-      {0, 8, 20},
-      {0, 3, 54},
-      {0, 1, 19},
-      {0, 10, 46},
-      {0, 11, 9},
-      {0, 39, 28},
-      {0, 53, 49},
-      {0, 12, 2},
-      {0, 34, 4},
-      {0, 47, 36},
-      {0, 23, 45},
-      {0, 5, 37},
-      {0, 24, 38},
-      {0, 43, 26},
-      {0, 48, 51},
-      {0, 44, 32},
-      {0, 15, 16},
-      {0, 57, 22},
-      {0, 55, 50},
-      {0, 29, 58},
-      {0, 60, 59},
-      {0, 41, 61},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 13, 70},
-      {0, 71, 7},
-      {0, 42, 31},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 21, 30},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 82, 18},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 52, 91},
-      {0, 6, 92},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 99, 40},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 56, 110},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 4), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(155, {
-      {0, 0, 0},
-      {18776483, 0, 0},
-      {37009196, 0, 0},
-      {277023757, 0, 0},
-      {296981500, 0, 0},
-      {348988933, 0, 0},
-      {451264926, 0, 0},
-      {564884461, 0, 0},
-      {804899022, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {876864198, 0, 0},
-      {900522183, 0, 0},
-      {928261291, 0, 0},
-      {959681532, 0, 0},
-      {1164724902, 0, 0},
-      {1323407757, 0, 0},
-      {1332774287, 0, 0},
-      {1404739463, 0, 0},
-      {1447712361, 0, 0},
-      {1450415100, 0, 0},
-      {1513770932, 0, 0},
-      {1620634991, 0, 0},
-      {1692600167, 0, 0},
-      {1860649552, 0, 0},
-      {1932614728, 0, 0},
-      {2087004702, 0, 0},
-      {2148510256, 0, 0},
-      {2220475432, 0, 0},
-      {2388524817, 0, 0},
-      {2460489993, 0, 0},
-      {2676385521, 0, 0},
-      {2748350697, 0, 0},
-      {2855506940, 0, 0},
-      {2860348412, 0, 0},
-      {2916400082, 0, 0},
-      {2988365258, 0, 0},
-      {3061856840, 0, 0},
-      {3063508455, 0, 0},
-      {3065160070, 0, 0},
-      {3066811685, 0, 0},
-      {3068463300, 0, 0},
-      {3070114915, 0, 0},
-      {3071766530, 0, 0},
-      {3073418145, 0, 0},
-      {3075069760, 0, 0},
-      {3076721375, 0, 0},
-      {3078372990, 0, 0},
-      {3080024605, 0, 0},
-      {3081676220, 0, 0},
-      {3083327835, 0, 0},
-      {3084979450, 0, 0},
-      {3086631065, 0, 0},
-      {3088282680, 0, 0},
-      {3114708520, 0, 0},
-      {3116360135, 0, 0},
-      {3118011750, 0, 0},
-      {3119663365, 0, 0},
-      {3121314980, 0, 0},
-      {3124618210, 0, 0},
-      {3126269825, 0, 0},
-      {3127921440, 0, 0},
-      {3129573055, 0, 0},
-      {3131224670, 0, 0},
-      {3132876285, 0, 0},
-      {3134527900, 0, 0},
-      {3136179515, 0, 0},
-      {3204260786, 0, 0},
-      {3264086791, 0, 0},
-      {3276225962, 0, 0},
-      {3444275347, 0, 0},
-      {3516240523, 0, 0},
-      {3588205699, 0, 0},
-      {3732136051, 0, 0},
-      {3804101227, 0, 0},
-      {3874089391, 0, 0},
-      {4044115788, 0, 0},
-      {4116080964, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 45, 43},
-      {0, 3, 46},
-      {0, 71, 36},
-      {0, 44, 34},
-      {0, 76, 54},
-      {0, 73, 55},
-      {0, 57, 67},
-      {0, 51, 56},
-      {0, 31, 27},
-      {0, 38, 37},
-      {0, 40, 39},
-      {0, 42, 41},
-      {0, 49, 47},
-      {0, 35, 50},
-      {0, 21, 70},
-      {0, 19, 5},
-      {0, 8, 58},
-      {0, 17, 11},
-      {0, 24, 18},
-      {0, 30, 29},
-      {0, 52, 9},
-      {0, 77, 22},
-      {0, 62, 48},
-      {0, 25, 53},
-      {0, 20, 59},
-      {0, 26, 60},
-      {0, 72, 6},
-      {0, 79, 69},
-      {0, 80, 7},
-      {0, 81, 2},
-      {0, 12, 13},
-      {0, 82, 68},
-      {0, 65, 61},
-      {0, 74, 63},
-      {0, 23, 83},
-      {0, 64, 10},
-      {0, 84, 32},
-      {0, 66, 28},
-      {0, 15, 85},
-      {0, 86, 16},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 1, 93},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 100, 75},
-      {0, 102, 101},
-      {0, 104, 103},
-      {0, 106, 105},
-      {0, 107, 14},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 129, 128},
-      {0, 131, 130},
-      {0, 133, 132},
-      {0, 135, 134},
-      {0, 137, 136},
-      {0, 139, 138},
-      {0, 141, 140},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 33, 148},
-      {0, 4, 149},
-      {0, 78, 150},
-      {0, 152, 151},
-      {0, 154, 153},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeConstruct, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {789872778, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 2},
-      {0, 4, 7},
-      {0, 1, 8},
-      {0, 9, 5},
-      {0, 3, 10},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(49, {
-      {0, 0, 0},
-      {126463145, 0, 0},
-      {171307615, 0, 0},
-      {342159236, 0, 0},
-      {354479447, 0, 0},
-      {593829839, 0, 0},
-      {743407979, 0, 0},
-      {898191441, 0, 0},
-      {900522183, 0, 0},
-      {1265796414, 0, 0},
-      {1287304304, 0, 0},
-      {1356063462, 0, 0},
-      {1368383673, 0, 0},
-      {1526654696, 0, 0},
-      {1766994680, 0, 0},
-      {1793544760, 0, 0},
-      {1811839150, 0, 0},
-      {2234361374, 0, 0},
-      {2279700640, 0, 0},
-      {2383939514, 0, 0},
-      {2780898906, 0, 0},
-      {2996594997, 0, 0},
-      {3413713311, 0, 0},
-      {3554463148, 0, 0},
-      {3635542517, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 11, 15},
-      {0, 20, 14},
-      {0, 7, 18},
-      {0, 6, 1},
-      {0, 12, 10},
-      {0, 23, 19},
-      {0, 13, 5},
-      {0, 24, 17},
-      {0, 21, 3},
-      {0, 22, 16},
-      {0, 26, 2},
-      {0, 27, 8},
-      {0, 4, 28},
-      {0, 29, 9},
-      {0, 31, 30},
-      {0, 33, 32},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 41, 40},
-      {0, 43, 42},
-      {0, 45, 44},
-      {0, 47, 46},
-      {0, 25, 48},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(153, {
-      {0, 0, 0},
-      {13107491, 0, 0},
-      {257136089, 0, 0},
-      {293528591, 0, 0},
-      {321459212, 0, 0},
-      {425022309, 0, 0},
-      {490769168, 0, 0},
-      {495107308, 0, 0},
-      {517919178, 0, 0},
-      {617312262, 0, 0},
-      {708736129, 0, 0},
-      {753756604, 0, 0},
-      {765238787, 0, 0},
-      {796985462, 0, 0},
-      {819503463, 0, 0},
-      {850497536, 0, 0},
-      {948086521, 0, 0},
-      {1004589179, 0, 0},
-      {1120149824, 0, 0},
-      {1165671422, 0, 0},
-      {1203545131, 0, 0},
-      {1297165140, 0, 0},
-      {1335363438, 0, 0},
-      {1351676723, 0, 0},
-      {1391866096, 0, 0},
-      {1584369690, 0, 0},
-      {1631216488, 0, 0},
-      {1691646294, 0, 0},
-      {1779143013, 0, 0},
-      {1858116930, 0, 0},
-      {1890300748, 0, 0},
-      {1915438939, 0, 0},
-      {1918742169, 0, 0},
-      {1922045399, 0, 0},
-      {1961990747, 0, 0},
-      {2037710159, 0, 0},
-      {2037814253, 0, 0},
-      {2043873558, 0, 0},
-      {2096388952, 0, 0},
-      {2169307971, 0, 0},
-      {2257843797, 0, 0},
-      {2262220987, 0, 0},
-      {2338272340, 0, 0},
-      {2405770322, 0, 0},
-      {2498042266, 0, 0},
-      {2563789125, 0, 0},
-      {2588618056, 0, 0},
-      {2645120714, 0, 0},
-      {2864863800, 0, 0},
-      {2909957084, 0, 0},
-      {2975894973, 0, 0},
-      {3041450802, 0, 0},
-      {3151638847, 0, 0},
-      {3187066832, 0, 0},
-      {3244716568, 0, 0},
-      {3271748023, 0, 0},
-      {3304438238, 0, 0},
-      {3312467582, 0, 0},
-      {3325419312, 0, 0},
-      {3370185097, 0, 0},
-      {3419674548, 0, 0},
-      {3435931956, 0, 0},
-      {3504158761, 0, 0},
-      {3602522282, 0, 0},
-      {3653059026, 0, 0},
-      {3716353056, 0, 0},
-      {3782099915, 0, 0},
-      {3838648480, 0, 0},
-      {3847846774, 0, 0},
-      {3913593633, 0, 0},
-      {3989799199, 0, 0},
-      {3997038726, 0, 0},
-      {4046301857, 0, 0},
-      {4092654294, 0, 0},
-      {4176581069, 0, 0},
-      {4242327928, 0, 0},
-      {4285652249, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 74, 38},
-      {0, 12, 56},
-      {0, 28, 24},
-      {0, 60, 43},
-      {0, 65, 72},
-      {0, 18, 2},
-      {0, 52, 3},
-      {0, 19, 10},
-      {0, 49, 36},
-      {0, 67, 66},
-      {0, 41, 17},
-      {0, 53, 11},
-      {0, 29, 68},
-      {0, 26, 55},
-      {0, 70, 76},
-      {0, 73, 47},
-      {0, 51, 22},
-      {0, 39, 21},
-      {0, 5, 9},
-      {0, 40, 48},
-      {0, 59, 44},
-      {0, 6, 69},
-      {0, 32, 31},
-      {0, 4, 33},
-      {0, 13, 54},
-      {0, 14, 50},
-      {0, 35, 75},
-      {0, 58, 23},
-      {0, 16, 34},
-      {0, 27, 63},
-      {0, 45, 61},
-      {0, 20, 46},
-      {0, 71, 1},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 84, 8},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 64, 99},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 106, 62},
-      {0, 108, 107},
-      {0, 110, 109},
-      {0, 7, 111},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 30, 124},
-      {0, 126, 125},
-      {0, 128, 127},
-      {0, 130, 129},
-      {0, 132, 131},
-      {0, 134, 133},
-      {0, 135, 25},
-      {0, 57, 136},
-      {0, 138, 137},
-      {0, 42, 139},
-      {0, 37, 140},
-      {0, 142, 141},
-      {0, 143, 15},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 149, 148},
-      {0, 151, 150},
-      {0, 152, 77},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeExtract, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(47, {
-      {0, 0, 0},
-      {545678922, 0, 0},
-      {630592085, 0, 0},
-      {679771963, 0, 0},
-      {899570100, 0, 0},
-      {906176560, 0, 0},
-      {929101967, 0, 0},
-      {1100599986, 0, 0},
-      {1103903216, 0, 0},
-      {1107206446, 0, 0},
-      {1369578001, 0, 0},
-      {1372881231, 0, 0},
-      {2320303498, 0, 0},
-      {2926633629, 0, 0},
-      {3249265647, 0, 0},
-      {3334207724, 0, 0},
-      {3486057732, 0, 0},
-      {3674863070, 0, 0},
-      {3705139860, 0, 0},
-      {3800912395, 0, 0},
-      {3802564010, 0, 0},
-      {3822983876, 0, 0},
-      {4141567741, 0, 0},
-      {4292991777, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 9, 17},
-      {0, 20, 11},
-      {0, 25, 5},
-      {0, 2, 14},
-      {0, 23, 13},
-      {0, 16, 26},
-      {0, 27, 24},
-      {0, 28, 8},
-      {0, 29, 18},
-      {0, 22, 30},
-      {0, 6, 31},
-      {0, 21, 32},
-      {0, 3, 33},
-      {0, 35, 34},
-      {0, 1, 12},
-      {0, 10, 36},
-      {0, 37, 19},
-      {0, 4, 15},
-      {0, 39, 38},
-      {0, 7, 40},
-      {0, 42, 41},
-      {0, 44, 43},
-      {0, 46, 45},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
-      {0, 0, 0},
-      {125792961, 0, 0},
-      {132755933, 0, 0},
-      {156014509, 0, 0},
-      {436066778, 0, 0},
-      {463084678, 0, 0},
-      {531559080, 0, 0},
-      {565233904, 0, 0},
-      {578132535, 0, 0},
-      {600906020, 0, 0},
-      {602222721, 0, 0},
-      {694743357, 0, 0},
-      {760554870, 0, 0},
-      {996663016, 0, 0},
-      {1022309772, 0, 0},
-      {1351676723, 0, 0},
-      {1496901698, 0, 0},
-      {1502470404, 0, 0},
-      {1522901980, 0, 0},
-      {1548254487, 0, 0},
-      {1637661947, 0, 0},
-      {1788504755, 0, 0},
-      {2092468906, 0, 0},
-      {2094647776, 0, 0},
-      {2127660080, 0, 0},
-      {2213946343, 0, 0},
-      {2225172640, 0, 0},
-      {2259467579, 0, 0},
-      {2263866576, 0, 0},
-      {2600961503, 0, 0},
-      {2727022058, 0, 0},
-      {2752967311, 0, 0},
-      {2864705739, 0, 0},
-      {3021406120, 0, 0},
-      {3044723416, 0, 0},
-      {3052439312, 0, 0},
-      {3136865519, 0, 0},
-      {3297860332, 0, 0},
-      {3352361837, 0, 0},
-      {3670298840, 0, 0},
-      {3712946115, 0, 0},
-      {3732709413, 0, 0},
-      {3764662384, 0, 0},
-      {3788324110, 0, 0},
-      {3928555688, 0, 0},
-      {4083347580, 0, 0},
-      {4098876453, 0, 0},
-      {4147239510, 0, 0},
-      {4199470013, 0, 0},
-      {4211577142, 0, 0},
-      {4218799564, 0, 0},
-      {4290374884, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 2},
-      {0, 9, 8},
-      {0, 17, 10},
-      {0, 20, 18},
-      {0, 22, 21},
-      {0, 26, 23},
-      {0, 31, 29},
-      {0, 35, 34},
-      {0, 45, 36},
-      {0, 5, 3},
-      {0, 12, 6},
-      {0, 15, 14},
-      {0, 25, 19},
-      {0, 28, 27},
-      {0, 38, 33},
-      {0, 43, 39},
-      {0, 47, 46},
-      {0, 50, 49},
-      {0, 7, 51},
-      {0, 1, 48},
-      {0, 37, 24},
-      {0, 44, 42},
-      {0, 13, 11},
-      {0, 41, 40},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 30, 16},
-      {0, 73, 32},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 52, 102},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(93, {
-      {0, 0, 0},
-      {17185761, 0, 0},
-      {117250846, 0, 0},
-      {296981500, 0, 0},
-      {330388453, 0, 0},
-      {346929928, 0, 0},
-      {533021259, 0, 0},
-      {564302770, 0, 0},
-      {680157484, 0, 0},
-      {721450866, 0, 0},
-      {798549062, 0, 0},
-      {853200279, 0, 0},
-      {864295921, 0, 0},
-      {900522183, 0, 0},
-      {973908139, 0, 0},
-      {983243705, 0, 0},
-      {1033363654, 0, 0},
-      {1037370721, 0, 0},
-      {1464587427, 0, 0},
-      {1670691893, 0, 0},
-      {1686512349, 0, 0},
-      {1849065716, 0, 0},
-      {1917602962, 0, 0},
-      {1965902997, 0, 0},
-      {2121980967, 0, 0},
-      {2311072371, 0, 0},
-      {2339901602, 0, 0},
-      {2517964682, 0, 0},
-      {2542834724, 0, 0},
-      {2558655180, 0, 0},
-      {2736881867, 0, 0},
-      {2855506940, 0, 0},
-      {2888753905, 0, 0},
-      {2950446516, 0, 0},
-      {3044188332, 0, 0},
-      {3079287749, 0, 0},
-      {3153451899, 0, 0},
-      {3214537066, 0, 0},
-      {3234673086, 0, 0},
-      {3349230696, 0, 0},
-      {3504158761, 0, 0},
-      {3570411982, 0, 0},
-      {3652695478, 0, 0},
-      {3764205609, 0, 0},
-      {3940720663, 0, 0},
-      {4180570743, 0, 0},
-      {4221373527, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 24, 18},
-      {0, 4, 2},
-      {0, 15, 14},
-      {0, 21, 20},
-      {0, 29, 26},
-      {0, 42, 36},
-      {0, 7, 45},
-      {0, 37, 9},
-      {0, 8, 5},
-      {0, 32, 11},
-      {0, 39, 38},
-      {0, 12, 10},
-      {0, 28, 19},
-      {0, 1, 46},
-      {0, 17, 6},
-      {0, 30, 23},
-      {0, 44, 33},
-      {0, 35, 13},
-      {0, 16, 48},
-      {0, 50, 49},
-      {0, 52, 51},
-      {0, 54, 53},
-      {0, 55, 40},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 25, 22},
-      {0, 63, 62},
-      {0, 3, 64},
-      {0, 66, 65},
-      {0, 68, 67},
-      {0, 70, 69},
-      {0, 34, 71},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 27, 43},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 84, 31},
-      {0, 86, 85},
-      {0, 41, 87},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 47, 92},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(115, {
-      {0, 0, 0},
-      {132755933, 0, 0},
-      {156014509, 0, 0},
-      {255227811, 0, 0},
-      {371186900, 0, 0},
-      {371428004, 0, 0},
-      {374731234, 0, 0},
-      {531559080, 0, 0},
-      {565233904, 0, 0},
-      {578132535, 0, 0},
-      {591140762, 0, 0},
-      {600906020, 0, 0},
-      {602222721, 0, 0},
-      {656610661, 0, 0},
-      {760554870, 0, 0},
-      {996663016, 0, 0},
-      {1022309772, 0, 0},
-      {1496901698, 0, 0},
-      {1502470404, 0, 0},
-      {1522901980, 0, 0},
-      {1536350567, 0, 0},
-      {1543280290, 0, 0},
-      {1548254487, 0, 0},
-      {1788504755, 0, 0},
-      {2064733527, 0, 0},
-      {2092468906, 0, 0},
-      {2094647776, 0, 0},
-      {2162986400, 0, 0},
-      {2225172640, 0, 0},
-      {2259467579, 0, 0},
-      {2263866576, 0, 0},
-      {2360004627, 0, 0},
-      {2507709226, 0, 0},
-      {2600961503, 0, 0},
-      {2727022058, 0, 0},
-      {2752967311, 0, 0},
-      {2864705739, 0, 0},
-      {3021406120, 0, 0},
-      {3052439312, 0, 0},
-      {3136865519, 0, 0},
-      {3297860332, 0, 0},
-      {3352361837, 0, 0},
-      {3598957382, 0, 0},
-      {3619787319, 0, 0},
-      {3655201337, 0, 0},
-      {3670298840, 0, 0},
-      {3774892253, 0, 0},
-      {3788324110, 0, 0},
-      {3808408202, 0, 0},
-      {3951925872, 0, 0},
-      {3952316364, 0, 0},
-      {4098876453, 0, 0},
-      {4147239510, 0, 0},
-      {4199470013, 0, 0},
-      {4211577142, 0, 0},
-      {4217306348, 0, 0},
-      {4218799564, 0, 0},
-      {4290374884, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 43},
-      {0, 4, 1},
-      {0, 11, 9},
-      {0, 13, 12},
-      {0, 19, 18},
-      {0, 25, 23},
-      {0, 28, 26},
-      {0, 35, 33},
-      {0, 39, 38},
-      {0, 2, 49},
-      {0, 7, 3},
-      {0, 16, 14},
-      {0, 29, 22},
-      {0, 37, 30},
-      {0, 45, 41},
-      {0, 51, 47},
-      {0, 54, 52},
-      {0, 57, 56},
-      {0, 53, 8},
-      {0, 32, 10},
-      {0, 42, 40},
-      {0, 24, 46},
-      {0, 15, 50},
-      {0, 55, 20},
-      {0, 59, 44},
-      {0, 61, 60},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 31, 17},
-      {0, 36, 34},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 27, 82},
-      {0, 5, 21},
-      {0, 48, 83},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 58, 114},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpCompositeInsert, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {3866587616, 0, 0},
-      {3868239231, 0, 0},
-      {3869890846, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 4},
-      {0, 2, 5},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {1164218401, 0, 0},
-      {2036361232, 0, 0},
-      {2637132451, 0, 0},
-      {3237903670, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 3, 6},
-      {0, 1, 7},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {543558236, 0, 0},
-      {1069781886, 0, 0},
-      {1596005536, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 2, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1949759310, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpSampledImage, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
-      {0, 0, 0},
-      {236660303, 0, 0},
-      {347505241, 0, 0},
-      {426360862, 0, 0},
-      {439998433, 0, 0},
-      {488500848, 0, 0},
-      {495107308, 0, 0},
-      {868652905, 0, 0},
-      {1191735827, 0, 0},
-      {1265998516, 0, 0},
-      {1309728002, 0, 0},
-      {1365842164, 0, 0},
-      {1396344138, 0, 0},
-      {1508074873, 0, 0},
-      {1553476262, 0, 0},
-      {1642818143, 0, 0},
-      {1851510470, 0, 0},
-      {1858116930, 0, 0},
-      {1863199739, 0, 0},
-      {1979978194, 0, 0},
-      {1986584654, 0, 0},
-      {2092100514, 0, 0},
-      {2098706974, 0, 0},
-      {2231688008, 0, 0},
-      {2232491275, 0, 0},
-      {2329992200, 0, 0},
-      {2637935122, 0, 0},
-      {2693892518, 0, 0},
-      {2759250216, 0, 0},
-      {2839765116, 0, 0},
-      {2855895374, 0, 0},
-      {2913136690, 0, 0},
-      {3012980338, 0, 0},
-      {3327770644, 0, 0},
-      {3362344229, 0, 0},
-      {3398925952, 0, 0},
-      {3448018532, 0, 0},
-      {3457985288, 0, 0},
-      {3566035349, 0, 0},
-      {3657635382, 0, 0},
-      {3702405475, 0, 0},
-      {3757479030, 0, 0},
-      {3797204453, 0, 0},
-      {4291477370, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 32, 28},
-      {0, 9, 35},
-      {0, 31, 11},
-      {0, 10, 30},
-      {0, 25, 21},
-      {0, 40, 2},
-      {0, 15, 19},
-      {0, 24, 36},
-      {0, 42, 4},
-      {0, 18, 16},
-      {0, 29, 26},
-      {0, 43, 7},
-      {0, 45, 8},
-      {0, 37, 13},
-      {0, 47, 46},
-      {0, 48, 33},
-      {0, 49, 14},
-      {0, 3, 22},
-      {0, 50, 12},
-      {0, 41, 39},
-      {0, 51, 34},
-      {0, 52, 20},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 61, 23},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 27, 66},
-      {0, 67, 38},
-      {0, 68, 17},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 5, 77},
-      {0, 78, 1},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 83, 6},
-      {0, 85, 84},
-      {0, 44, 86},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(15, {
-      {0, 0, 0},
-      {883854656, 0, 0},
-      {1962971231, 0, 0},
-      {2036361232, 0, 0},
-      {2356768706, 0, 0},
-      {2637132451, 0, 0},
-      {3237903670, 0, 0},
-      {3829682756, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 8, 2},
-      {0, 6, 9},
-      {0, 10, 7},
-      {0, 4, 5},
-      {0, 12, 11},
-      {0, 3, 13},
-      {0, 14, 1},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(87, {
-      {0, 0, 0},
-      {150685616, 0, 0},
-      {255302575, 0, 0},
-      {414620710, 0, 0},
-      {557400685, 0, 0},
-      {575205902, 0, 0},
-      {618761615, 0, 0},
-      {646282397, 0, 0},
-      {686024761, 0, 0},
-      {740921498, 0, 0},
-      {921246433, 0, 0},
-      {1057578789, 0, 0},
-      {1162127370, 0, 0},
-      {1329499601, 0, 0},
-      {1352628475, 0, 0},
-      {1502028603, 0, 0},
-      {1519723107, 0, 0},
-      {1543798545, 0, 0},
-      {1545450160, 0, 0},
-      {1570165302, 0, 0},
-      {1600392975, 0, 0},
-      {1641415225, 0, 0},
-      {2204920111, 0, 0},
-      {2257971049, 0, 0},
-      {2276405827, 0, 0},
-      {2339018837, 0, 0},
-      {2340670452, 0, 0},
-      {2517964682, 0, 0},
-      {2532518896, 0, 0},
-      {2674090849, 0, 0},
-      {2754074729, 0, 0},
-      {2804281092, 0, 0},
-      {2816338013, 0, 0},
-      {2841008029, 0, 0},
-      {3234673086, 0, 0},
-      {3249261197, 0, 0},
-      {3619787319, 0, 0},
-      {3627739127, 0, 0},
-      {3669223677, 0, 0},
-      {3787567939, 0, 0},
-      {3898287302, 0, 0},
-      {4142016703, 0, 0},
-      {4237092412, 0, 0},
-      {4285779501, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 16, 15},
-      {0, 2, 33},
-      {0, 41, 35},
-      {0, 32, 30},
-      {0, 39, 38},
-      {0, 5, 1},
-      {0, 9, 43},
-      {0, 40, 22},
-      {0, 29, 12},
-      {0, 4, 3},
-      {0, 25, 37},
-      {0, 34, 26},
-      {0, 45, 19},
-      {0, 31, 24},
-      {0, 47, 46},
-      {0, 48, 20},
-      {0, 49, 6},
-      {0, 8, 21},
-      {0, 50, 11},
-      {0, 13, 10},
-      {0, 51, 42},
-      {0, 52, 23},
-      {0, 54, 53},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 61, 28},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 17, 66},
-      {0, 67, 18},
-      {0, 68, 7},
-      {0, 70, 69},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 14, 77},
-      {0, 78, 27},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 83, 36},
-      {0, 85, 84},
-      {0, 44, 86},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {2855506940, 0, 0},
-      {3266548732, 0, 0},
-      {3732640764, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 1},
-      {0, 5, 4},
-      {0, 3, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleImplicitLod, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(139, {
-      {0, 0, 0},
-      {27177503, 0, 0},
-      {30663912, 0, 0},
-      {151672195, 0, 0},
-      {162608772, 0, 0},
-      {180913835, 0, 0},
-      {371621315, 0, 0},
-      {414444763, 0, 0},
-      {421602934, 0, 0},
-      {443347828, 0, 0},
-      {458937500, 0, 0},
-      {587888644, 0, 0},
-      {601656217, 0, 0},
-      {665789406, 0, 0},
-      {712168842, 0, 0},
-      {730943059, 0, 0},
-      {750870327, 0, 0},
-      {875212982, 0, 0},
-      {899320334, 0, 0},
-      {973908139, 0, 0},
-      {989813600, 0, 0},
-      {1057606514, 0, 0},
-      {1171541710, 0, 0},
-      {1243764146, 0, 0},
-      {1310404265, 0, 0},
-      {1366337101, 0, 0},
-      {1443547269, 0, 0},
-      {1472185378, 0, 0},
-      {1473799048, 0, 0},
-      {1543935193, 0, 0},
-      {1572834111, 0, 0},
-      {1623013158, 0, 0},
-      {1686512349, 0, 0},
-      {1705716306, 0, 0},
-      {1747355813, 0, 0},
-      {1755165354, 0, 0},
-      {1781864804, 0, 0},
-      {1916983087, 0, 0},
-      {1941403425, 0, 0},
-      {2023008475, 0, 0},
-      {2043684541, 0, 0},
-      {2274226560, 0, 0},
-      {2285438321, 0, 0},
-      {2315690100, 0, 0},
-      {2344328209, 0, 0},
-      {2414725163, 0, 0},
-      {2493146691, 0, 0},
-      {2495155989, 0, 0},
-      {2558655180, 0, 0},
-      {2577859137, 0, 0},
-      {2857814560, 0, 0},
-      {2895151306, 0, 0},
-      {2986830770, 0, 0},
-      {3006548167, 0, 0},
-      {3127329373, 0, 0},
-      {3157581152, 0, 0},
-      {3216471040, 0, 0},
-      {3296722158, 0, 0},
-      {3367298820, 0, 0},
-      {3376009661, 0, 0},
-      {3450001968, 0, 0},
-      {3526837441, 0, 0},
-      {3609540589, 0, 0},
-      {3743398113, 0, 0},
-      {3858973601, 0, 0},
-      {3953984401, 0, 0},
-      {3999472204, 0, 0},
-      {4088613871, 0, 0},
-      {4184019303, 0, 0},
-      {4258229445, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 31, 16},
-      {0, 58, 47},
-      {0, 21, 61},
-      {0, 6, 14},
-      {0, 65, 23},
-      {0, 35, 5},
-      {0, 2, 7},
-      {0, 10, 25},
-      {0, 40, 22},
-      {0, 9, 50},
-      {0, 20, 11},
-      {0, 38, 36},
-      {0, 13, 12},
-      {0, 67, 28},
-      {0, 71, 68},
-      {0, 73, 72},
-      {0, 3, 29},
-      {0, 27, 8},
-      {0, 44, 37},
-      {0, 74, 63},
-      {0, 76, 75},
-      {0, 18, 1},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 26, 15},
-      {0, 83, 43},
-      {0, 85, 84},
-      {0, 19, 86},
-      {0, 48, 32},
-      {0, 33, 46},
-      {0, 87, 49},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 41, 30},
-      {0, 52, 42},
-      {0, 64, 55},
-      {0, 92, 53},
-      {0, 94, 93},
-      {0, 51, 39},
-      {0, 45, 95},
-      {0, 66, 54},
-      {0, 97, 96},
-      {0, 57, 98},
-      {0, 99, 69},
-      {0, 101, 100},
-      {0, 56, 102},
-      {0, 4, 59},
-      {0, 34, 17},
-      {0, 103, 24},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 60, 110},
-      {0, 111, 62},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 129, 128},
-      {0, 70, 130},
-      {0, 132, 131},
-      {0, 134, 133},
-      {0, 136, 135},
-      {0, 138, 137},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(11, {
-      {0, 0, 0},
-      {883854656, 0, 0},
-      {1962971231, 0, 0},
-      {2036361232, 0, 0},
-      {2366506734, 0, 0},
-      {3829682756, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 2},
-      {0, 6, 7},
-      {0, 8, 5},
-      {0, 3, 9},
-      {0, 1, 10},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(73, {
-      {0, 0, 0},
-      {178571546, 0, 0},
-      {223310468, 0, 0},
-      {388034151, 0, 0},
-      {449954059, 0, 0},
-      {694743357, 0, 0},
-      {797415788, 0, 0},
-      {835638766, 0, 0},
-      {1002144380, 0, 0},
-      {1221183390, 0, 0},
-      {1570165302, 0, 0},
-      {1663234329, 0, 0},
-      {1750829822, 0, 0},
-      {1894133125, 0, 0},
-      {1967643923, 0, 0},
-      {1980341560, 0, 0},
-      {2278706468, 0, 0},
-      {2326990117, 0, 0},
-      {2464905186, 0, 0},
-      {2511346984, 0, 0},
-      {2517964682, 0, 0},
-      {2616085763, 0, 0},
-      {2710583246, 0, 0},
-      {2745872368, 0, 0},
-      {2924263085, 0, 0},
-      {3027500544, 0, 0},
-      {3044723416, 0, 0},
-      {3202324433, 0, 0},
-      {3289213933, 0, 0},
-      {3323682385, 0, 0},
-      {3366848728, 0, 0},
-      {3417583519, 0, 0},
-      {3732916270, 0, 0},
-      {3787909072, 0, 0},
-      {3877813395, 0, 0},
-      {4028028350, 0, 0},
-      {4178218543, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 36, 31},
-      {0, 15, 3},
-      {0, 17, 1},
-      {0, 24, 12},
-      {0, 35, 34},
-      {0, 28, 27},
-      {0, 21, 38},
-      {0, 6, 13},
-      {0, 14, 7},
-      {0, 39, 25},
-      {0, 40, 30},
-      {0, 42, 41},
-      {0, 32, 43},
-      {0, 23, 9},
-      {0, 11, 44},
-      {0, 45, 22},
-      {0, 47, 46},
-      {0, 2, 16},
-      {0, 49, 48},
-      {0, 4, 50},
-      {0, 51, 18},
-      {0, 53, 52},
-      {0, 33, 54},
-      {0, 26, 55},
-      {0, 57, 56},
-      {0, 5, 58},
-      {0, 59, 8},
-      {0, 19, 60},
-      {0, 10, 61},
-      {0, 29, 62},
-      {0, 37, 63},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 20, 68},
-      {0, 70, 69},
-      {0, 72, 71},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {2855506940, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 5), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(13, {
-      {0, 0, 0},
-      {3533637837, 0, 0},
-      {3535289452, 0, 0},
-      {3536941067, 0, 0},
-      {3538592682, 0, 0},
-      {3540244297, 0, 0},
-      {3541895912, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 7},
-      {0, 2, 8},
-      {0, 9, 3},
-      {0, 4, 10},
-      {0, 5, 11},
-      {0, 12, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpImageSampleExplicitLod, 6), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 2, 6},
-      {0, 1, 3},
-      {0, 8, 7},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(45, {
-      {0, 0, 0},
-      {328661377, 0, 0},
-      {464259778, 0, 0},
-      {920941800, 0, 0},
-      {969500141, 0, 0},
-      {1449907751, 0, 0},
-      {1451831482, 0, 0},
-      {1543798545, 0, 0},
-      {1545450160, 0, 0},
-      {1626224034, 0, 0},
-      {1669930486, 0, 0},
-      {1770165905, 0, 0},
-      {2278571792, 0, 0},
-      {2432827426, 0, 0},
-      {2656211099, 0, 0},
-      {2736844435, 0, 0},
-      {2870852215, 0, 0},
-      {2919626325, 0, 0},
-      {2923708820, 0, 0},
-      {3325419312, 0, 0},
-      {3678875745, 0, 0},
-      {4182141402, 0, 0},
-      {4241374559, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 2, 6},
-      {0, 9, 13},
-      {0, 5, 15},
-      {0, 4, 11},
-      {0, 20, 22},
-      {0, 10, 1},
-      {0, 18, 14},
-      {0, 16, 3},
-      {0, 12, 21},
-      {0, 8, 7},
-      {0, 24, 17},
-      {0, 19, 25},
-      {0, 27, 26},
-      {0, 29, 28},
-      {0, 31, 30},
-      {0, 33, 32},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 41, 40},
-      {0, 43, 42},
-      {0, 23, 44},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(89, {
-      {0, 0, 0},
-      {135920445, 0, 0},
-      {176166202, 0, 0},
-      {294390719, 0, 0},
-      {296981500, 0, 0},
-      {743407979, 0, 0},
-      {810488476, 0, 0},
-      {837715723, 0, 0},
-      {885020215, 0, 0},
-      {922996215, 0, 0},
-      {959681532, 0, 0},
-      {963902061, 0, 0},
-      {1136775085, 0, 0},
-      {1189681639, 0, 0},
-      {1203545131, 0, 0},
-      {1297294717, 0, 0},
-      {1317058015, 0, 0},
-      {1352397672, 0, 0},
-      {1367301635, 0, 0},
-      {1412908157, 0, 0},
-      {1570165302, 0, 0},
-      {1763758554, 0, 0},
-      {1791427568, 0, 0},
-      {1992893964, 0, 0},
-      {2013867381, 0, 0},
-      {2096388952, 0, 0},
-      {2219733501, 0, 0},
-      {2383939514, 0, 0},
-      {2517964682, 0, 0},
-      {2555315060, 0, 0},
-      {2572638469, 0, 0},
-      {2762094724, 0, 0},
-      {2770161927, 0, 0},
-      {2855506940, 0, 0},
-      {3044188332, 0, 0},
-      {3187066832, 0, 0},
-      {3319278167, 0, 0},
-      {3653838348, 0, 0},
-      {3675926744, 0, 0},
-      {3701632935, 0, 0},
-      {3712946115, 0, 0},
-      {3732709413, 0, 0},
-      {3743748793, 0, 0},
-      {3783543823, 0, 0},
-      {3930727258, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 15, 12},
-      {0, 38, 16},
-      {0, 41, 40},
-      {0, 1, 33},
-      {0, 21, 34},
-      {0, 9, 2},
-      {0, 24, 7},
-      {0, 39, 44},
-      {0, 29, 22},
-      {0, 17, 19},
-      {0, 36, 32},
-      {0, 26, 18},
-      {0, 30, 3},
-      {0, 11, 8},
-      {0, 42, 35},
-      {0, 46, 31},
-      {0, 27, 5},
-      {0, 48, 47},
-      {0, 28, 49},
-      {0, 51, 50},
-      {0, 52, 23},
-      {0, 54, 53},
-      {0, 13, 14},
-      {0, 6, 55},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 60, 43},
-      {0, 62, 61},
-      {0, 37, 63},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 70, 4},
-      {0, 10, 71},
-      {0, 72, 20},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 81, 25},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 45, 88},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
-      {0, 0, 0},
-      {126463145, 0, 0},
-      {220008971, 0, 0},
-      {246375791, 0, 0},
-      {503145996, 0, 0},
-      {628331516, 0, 0},
-      {643418617, 0, 0},
-      {743407979, 0, 0},
-      {837715723, 0, 0},
-      {858902117, 0, 0},
-      {870594305, 0, 0},
-      {939671928, 0, 0},
-      {959681532, 0, 0},
-      {1051471757, 0, 0},
-      {1092948665, 0, 0},
-      {1097775533, 0, 0},
-      {1136775085, 0, 0},
-      {1140367371, 0, 0},
-      {1332643570, 0, 0},
-      {1367301635, 0, 0},
-      {1558001705, 0, 0},
-      {1684282922, 0, 0},
-      {2096388952, 0, 0},
-      {2183547611, 0, 0},
-      {2219733501, 0, 0},
-      {2358141757, 0, 0},
-      {2359973133, 0, 0},
-      {2383939514, 0, 0},
-      {2444465148, 0, 0},
-      {2517964682, 0, 0},
-      {2567901801, 0, 0},
-      {2598189097, 0, 0},
-      {2655147757, 0, 0},
-      {2683080096, 0, 0},
-      {2705434194, 0, 0},
-      {2738307068, 0, 0},
-      {2780898906, 0, 0},
-      {3030911670, 0, 0},
-      {3032677281, 0, 0},
-      {3063300848, 0, 0},
-      {3277199633, 0, 0},
-      {3289969989, 0, 0},
-      {3401762422, 0, 0},
-      {3436143898, 0, 0},
-      {3560552546, 0, 0},
-      {3656163446, 0, 0},
-      {3675926744, 0, 0},
-      {3701632935, 0, 0},
-      {3743748793, 0, 0},
-      {3752211294, 0, 0},
-      {3794803132, 0, 0},
-      {4241374559, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 21},
-      {0, 17, 11},
-      {0, 36, 35},
-      {0, 46, 45},
-      {0, 50, 49},
-      {0, 9, 3},
-      {0, 20, 47},
-      {0, 37, 31},
-      {0, 2, 34},
-      {0, 40, 13},
-      {0, 51, 32},
-      {0, 41, 10},
-      {0, 38, 19},
-      {0, 18, 44},
-      {0, 43, 16},
-      {0, 48, 24},
-      {0, 26, 5},
-      {0, 53, 8},
-      {0, 15, 7},
-      {0, 25, 23},
-      {0, 54, 27},
-      {0, 56, 55},
-      {0, 58, 57},
-      {0, 60, 59},
-      {0, 39, 42},
-      {0, 62, 61},
-      {0, 30, 63},
-      {0, 4, 64},
-      {0, 65, 28},
-      {0, 66, 22},
-      {0, 68, 67},
-      {0, 69, 14},
-      {0, 70, 33},
-      {0, 71, 6},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 29, 76},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 84, 83},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 91, 12},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 52, 102},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFAdd, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 5},
-      {0, 4, 6},
-      {0, 1, 7},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(159, {
-      {0, 0, 0},
-      {50385656, 0, 0},
-      {117250846, 0, 0},
-      {171494987, 0, 0},
-      {195244192, 0, 0},
-      {210754155, 0, 0},
-      {265392489, 0, 0},
-      {333855951, 0, 0},
-      {416853049, 0, 0},
-      {529068443, 0, 0},
-      {533021259, 0, 0},
-      {615982737, 0, 0},
-      {660038281, 0, 0},
-      {663341511, 0, 0},
-      {669812542, 0, 0},
-      {716890919, 0, 0},
-      {1081536219, 0, 0},
-      {1119744229, 0, 0},
-      {1123617794, 0, 0},
-      {1139547465, 0, 0},
-      {1162789888, 0, 0},
-      {1178317551, 0, 0},
-      {1190147516, 0, 0},
-      {1193734351, 0, 0},
-      {1215030156, 0, 0},
-      {1220749418, 0, 0},
-      {1318479490, 0, 0},
-      {1461398554, 0, 0},
-      {1486207619, 0, 0},
-      {1551372768, 0, 0},
-      {1763758554, 0, 0},
-      {1797960910, 0, 0},
-      {1850331254, 0, 0},
-      {1894417995, 0, 0},
-      {1964254745, 0, 0},
-      {1965902997, 0, 0},
-      {1989327599, 0, 0},
-      {2095027856, 0, 0},
-      {2123683379, 0, 0},
-      {2124837447, 0, 0},
-      {2137526937, 0, 0},
-      {2269114589, 0, 0},
-      {2269130237, 0, 0},
-      {2330636993, 0, 0},
-      {2481746922, 0, 0},
-      {2503770904, 0, 0},
-      {2589449658, 0, 0},
-      {2603020391, 0, 0},
-      {2604576561, 0, 0},
-      {2795773560, 0, 0},
-      {2835131395, 0, 0},
-      {2852854788, 0, 0},
-      {2890638791, 0, 0},
-      {2895413148, 0, 0},
-      {2950446516, 0, 0},
-      {2963744582, 0, 0},
-      {3079287749, 0, 0},
-      {3088785099, 0, 0},
-      {3280064277, 0, 0},
-      {3335250889, 0, 0},
-      {3510242586, 0, 0},
-      {3517169445, 0, 0},
-      {3518703473, 0, 0},
-      {3536471583, 0, 0},
-      {3579593979, 0, 0},
-      {3591222197, 0, 0},
-      {3673811979, 0, 0},
-      {3727034815, 0, 0},
-      {3730093054, 0, 0},
-      {3898287302, 0, 0},
-      {3944781937, 0, 0},
-      {3950980241, 0, 0},
-      {4033586023, 0, 0},
-      {4041974454, 0, 0},
-      {4052965752, 0, 0},
-      {4083161638, 0, 0},
-      {4167600590, 0, 0},
-      {4185661467, 0, 0},
-      {4237092412, 0, 0},
-      {4244540017, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 44, 18},
-      {0, 69, 57},
-      {0, 24, 16},
-      {0, 79, 5},
-      {0, 59, 4},
-      {0, 76, 40},
-      {0, 53, 45},
-      {0, 14, 2},
-      {0, 62, 61},
-      {0, 33, 75},
-      {0, 38, 37},
-      {0, 42, 58},
-      {0, 66, 47},
-      {0, 63, 67},
-      {0, 1, 7},
-      {0, 10, 3},
-      {0, 13, 12},
-      {0, 23, 22},
-      {0, 32, 28},
-      {0, 36, 35},
-      {0, 72, 49},
-      {0, 74, 73},
-      {0, 77, 55},
-      {0, 27, 41},
-      {0, 31, 15},
-      {0, 6, 54},
-      {0, 78, 17},
-      {0, 81, 56},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 48, 30},
-      {0, 71, 60},
-      {0, 65, 51},
-      {0, 87, 86},
-      {0, 50, 34},
-      {0, 89, 88},
-      {0, 90, 9},
-      {0, 25, 8},
-      {0, 92, 91},
-      {0, 93, 26},
-      {0, 95, 94},
-      {0, 52, 39},
-      {0, 29, 20},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 64, 102},
-      {0, 104, 103},
-      {0, 106, 105},
-      {0, 21, 107},
-      {0, 108, 68},
-      {0, 109, 46},
-      {0, 110, 11},
-      {0, 112, 111},
-      {0, 114, 113},
-      {0, 116, 115},
-      {0, 117, 70},
-      {0, 43, 118},
-      {0, 120, 119},
-      {0, 122, 121},
-      {0, 124, 123},
-      {0, 126, 125},
-      {0, 128, 127},
-      {0, 129, 19},
-      {0, 131, 130},
-      {0, 133, 132},
-      {0, 135, 134},
-      {0, 137, 136},
-      {0, 139, 138},
-      {0, 141, 140},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 149, 148},
-      {0, 151, 150},
-      {0, 153, 152},
-      {0, 155, 154},
-      {0, 157, 156},
-      {0, 158, 80},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(103, {
-      {0, 0, 0},
-      {50998433, 0, 0},
-      {171494987, 0, 0},
-      {249378857, 0, 0},
-      {296981500, 0, 0},
-      {508007510, 0, 0},
-      {610429940, 0, 0},
-      {660038281, 0, 0},
-      {663341511, 0, 0},
-      {836581417, 0, 0},
-      {1027242654, 0, 0},
-      {1167160774, 0, 0},
-      {1191015885, 0, 0},
-      {1200870684, 0, 0},
-      {1203545131, 0, 0},
-      {1265796414, 0, 0},
-      {1319785741, 0, 0},
-      {1669959736, 0, 0},
-      {1684282922, 0, 0},
-      {1752686878, 0, 0},
-      {1850331254, 0, 0},
-      {1901166356, 0, 0},
-      {1906988301, 0, 0},
-      {2055836767, 0, 0},
-      {2095027856, 0, 0},
-      {2096388952, 0, 0},
-      {2144962711, 0, 0},
-      {2217833278, 0, 0},
-      {2500819054, 0, 0},
-      {2525173102, 0, 0},
-      {2575525651, 0, 0},
-      {2660843182, 0, 0},
-      {2855506940, 0, 0},
-      {2918750759, 0, 0},
-      {2919787747, 0, 0},
-      {3091876332, 0, 0},
-      {3187066832, 0, 0},
-      {3244209297, 0, 0},
-      {3423702268, 0, 0},
-      {3508792859, 0, 0},
-      {3548535223, 0, 0},
-      {3619787319, 0, 0},
-      {3653838348, 0, 0},
-      {3692647551, 0, 0},
-      {3713290482, 0, 0},
-      {3753486980, 0, 0},
-      {3783756895, 0, 0},
-      {3797961332, 0, 0},
-      {3836822275, 0, 0},
-      {4043078107, 0, 0},
-      {4052965752, 0, 0},
-      {4091394002, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 31, 49},
-      {0, 24, 19},
-      {0, 46, 45},
-      {0, 6, 48},
-      {0, 12, 33},
-      {0, 17, 21},
-      {0, 43, 11},
-      {0, 7, 2},
-      {0, 9, 8},
-      {0, 28, 13},
-      {0, 44, 38},
-      {0, 30, 50},
-      {0, 26, 22},
-      {0, 29, 51},
-      {0, 34, 37},
-      {0, 53, 40},
-      {0, 23, 54},
-      {0, 55, 25},
-      {0, 27, 18},
-      {0, 1, 10},
-      {0, 57, 56},
-      {0, 59, 58},
-      {0, 5, 47},
-      {0, 60, 20},
-      {0, 62, 61},
-      {0, 64, 63},
-      {0, 66, 65},
-      {0, 67, 39},
-      {0, 69, 68},
-      {0, 16, 70},
-      {0, 3, 71},
-      {0, 73, 72},
-      {0, 41, 15},
-      {0, 35, 74},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 36, 79},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 14, 84},
-      {0, 86, 85},
-      {0, 88, 87},
-      {0, 32, 89},
-      {0, 42, 90},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 52, 99},
-      {0, 100, 4},
-      {0, 102, 101},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(157, {
-      {0, 0, 0},
-      {49456560, 0, 0},
-      {170690025, 0, 0},
-      {243178923, 0, 0},
-      {295017943, 0, 0},
-      {296981500, 0, 0},
-      {330249537, 0, 0},
-      {435256475, 0, 0},
-      {443558693, 0, 0},
-      {456043370, 0, 0},
-      {470277359, 0, 0},
-      {592180731, 0, 0},
-      {663258455, 0, 0},
-      {706238670, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {877895868, 0, 0},
-      {900522183, 0, 0},
-      {1077859090, 0, 0},
-      {1082941229, 0, 0},
-      {1104362365, 0, 0},
-      {1132589448, 0, 0},
-      {1173092699, 0, 0},
-      {1203545131, 0, 0},
-      {1265796414, 0, 0},
-      {1278818058, 0, 0},
-      {1285705317, 0, 0},
-      {1319785741, 0, 0},
-      {1382106590, 0, 0},
-      {1461897718, 0, 0},
-      {1474506522, 0, 0},
-      {1530183840, 0, 0},
-      {1558001705, 0, 0},
-      {1558990974, 0, 0},
-      {1616846013, 0, 0},
-      {1633850097, 0, 0},
-      {1684282922, 0, 0},
-      {1725011064, 0, 0},
-      {1767704813, 0, 0},
-      {1923453688, 0, 0},
-      {1941148668, 0, 0},
-      {1955104493, 0, 0},
-      {2022961611, 0, 0},
-      {2162274327, 0, 0},
-      {2212501241, 0, 0},
-      {2219733501, 0, 0},
-      {2234361374, 0, 0},
-      {2272221101, 0, 0},
-      {2305269460, 0, 0},
-      {2488410748, 0, 0},
-      {2566666743, 0, 0},
-      {2598189097, 0, 0},
-      {2775815164, 0, 0},
-      {2793529873, 0, 0},
-      {2844616706, 0, 0},
-      {2970183398, 0, 0},
-      {3103302036, 0, 0},
-      {3110479131, 0, 0},
-      {3115038057, 0, 0},
-      {3116932970, 0, 0},
-      {3152745753, 0, 0},
-      {3187066832, 0, 0},
-      {3244209297, 0, 0},
-      {3383007207, 0, 0},
-      {3392887901, 0, 0},
-      {3508792859, 0, 0},
-      {3737376990, 0, 0},
-      {3753486980, 0, 0},
-      {3765247327, 0, 0},
-      {3817149113, 0, 0},
-      {3839047923, 0, 0},
-      {3886529747, 0, 0},
-      {4044928561, 0, 0},
-      {4061558677, 0, 0},
-      {4069720347, 0, 0},
-      {4069810315, 0, 0},
-      {4128942283, 0, 0},
-      {4164704452, 0, 0},
-      {4273793488, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 74, 47},
-      {0, 34, 33},
-      {0, 36, 14},
-      {0, 61, 48},
-      {0, 13, 31},
-      {0, 39, 25},
-      {0, 37, 29},
-      {0, 65, 54},
-      {0, 4, 73},
-      {0, 38, 10},
-      {0, 15, 43},
-      {0, 6, 35},
-      {0, 9, 16},
-      {0, 30, 19},
-      {0, 49, 44},
-      {0, 57, 53},
-      {0, 60, 58},
-      {0, 72, 66},
-      {0, 59, 76},
-      {0, 1, 68},
-      {0, 70, 42},
-      {0, 63, 3},
-      {0, 28, 69},
-      {0, 17, 55},
-      {0, 45, 64},
-      {0, 81, 80},
-      {0, 7, 82},
-      {0, 12, 11},
-      {0, 21, 50},
-      {0, 83, 18},
-      {0, 22, 84},
-      {0, 85, 26},
-      {0, 20, 86},
-      {0, 87, 40},
-      {0, 56, 88},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 93, 2},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 98, 41},
-      {0, 100, 99},
-      {0, 101, 52},
-      {0, 103, 102},
-      {0, 77, 71},
-      {0, 104, 78},
-      {0, 105, 46},
-      {0, 32, 8},
-      {0, 106, 51},
-      {0, 108, 107},
-      {0, 23, 109},
-      {0, 110, 27},
-      {0, 112, 111},
-      {0, 113, 75},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 124, 62},
-      {0, 126, 125},
-      {0, 128, 127},
-      {0, 67, 129},
-      {0, 131, 130},
-      {0, 5, 132},
-      {0, 134, 133},
-      {0, 136, 135},
-      {0, 138, 137},
-      {0, 139, 24},
-      {0, 141, 140},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 149, 148},
-      {0, 151, 150},
-      {0, 153, 152},
-      {0, 79, 154},
-      {0, 156, 155},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFSub, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 3, 6},
-      {0, 1, 7},
-      {0, 8, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(41, {
-      {0, 0, 0},
-      {342197850, 0, 0},
-      {885020215, 0, 0},
-      {963902061, 0, 0},
-      {1041368449, 0, 0},
-      {1352397672, 0, 0},
-      {1791427568, 0, 0},
-      {2013867381, 0, 0},
-      {2513230733, 0, 0},
-      {2555315060, 0, 0},
-      {2562485583, 0, 0},
-      {2567901801, 0, 0},
-      {2655147757, 0, 0},
-      {2680283743, 0, 0},
-      {2752766693, 0, 0},
-      {2806716850, 0, 0},
-      {3030911670, 0, 0},
-      {3401762422, 0, 0},
-      {3697738938, 0, 0},
-      {4164704452, 0, 0},
-      {4273793488, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 14, 10},
-      {0, 7, 16},
-      {0, 1, 15},
-      {0, 9, 6},
-      {0, 4, 12},
-      {0, 18, 5},
-      {0, 13, 2},
-      {0, 19, 3},
-      {0, 17, 20},
-      {0, 23, 22},
-      {0, 24, 8},
-      {0, 26, 25},
-      {0, 27, 11},
-      {0, 29, 28},
-      {0, 31, 30},
-      {0, 33, 32},
-      {0, 35, 34},
-      {0, 37, 36},
-      {0, 39, 38},
-      {0, 21, 40},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(129, {
-      {0, 0, 0},
-      {126463145, 0, 0},
-      {129135650, 0, 0},
-      {200922300, 0, 0},
-      {328661377, 0, 0},
-      {354479447, 0, 0},
-      {360730278, 0, 0},
-      {451264926, 0, 0},
-      {529068443, 0, 0},
-      {593829839, 0, 0},
-      {742917749, 0, 0},
-      {761731755, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {894529125, 0, 0},
-      {959681532, 0, 0},
-      {1054461787, 0, 0},
-      {1077859090, 0, 0},
-      {1086964761, 0, 0},
-      {1158929937, 0, 0},
-      {1168927492, 0, 0},
-      {1196280518, 0, 0},
-      {1203545131, 0, 0},
-      {1367301635, 0, 0},
-      {1508550646, 0, 0},
-      {1618544981, 0, 0},
-      {1661163736, 0, 0},
-      {1684282922, 0, 0},
-      {1766994680, 0, 0},
-      {1830851200, 0, 0},
-      {1901166356, 0, 0},
-      {1955104493, 0, 0},
-      {2055836767, 0, 0},
-      {2096388952, 0, 0},
-      {2100052708, 0, 0},
-      {2161102232, 0, 0},
-      {2197904616, 0, 0},
-      {2262137600, 0, 0},
-      {2278571792, 0, 0},
-      {2281956980, 0, 0},
-      {2438466459, 0, 0},
-      {2443959748, 0, 0},
-      {2517964682, 0, 0},
-      {2557754096, 0, 0},
-      {2622612602, 0, 0},
-      {2660843182, 0, 0},
-      {2736844435, 0, 0},
-      {2780898906, 0, 0},
-      {3044188332, 0, 0},
-      {3059119137, 0, 0},
-      {3194725903, 0, 0},
-      {3270430997, 0, 0},
-      {3337532056, 0, 0},
-      {3407526215, 0, 0},
-      {3496407048, 0, 0},
-      {3504158761, 0, 0},
-      {3534518722, 0, 0},
-      {3570411982, 0, 0},
-      {3701632935, 0, 0},
-      {3929248764, 0, 0},
-      {3944781937, 0, 0},
-      {3970432934, 0, 0},
-      {4008405264, 0, 0},
-      {4245257809, 0, 0},
-      {4253051659, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 6, 26},
-      {0, 46, 24},
-      {0, 64, 50},
-      {0, 7, 17},
-      {0, 40, 57},
-      {0, 56, 49},
-      {0, 34, 10},
-      {0, 32, 61},
-      {0, 36, 44},
-      {0, 8, 43},
-      {0, 4, 18},
-      {0, 25, 23},
-      {0, 9, 54},
-      {0, 45, 41},
-      {0, 13, 21},
-      {0, 47, 31},
-      {0, 39, 53},
-      {0, 11, 3},
-      {0, 29, 20},
-      {0, 38, 58},
-      {0, 37, 14},
-      {0, 66, 52},
-      {0, 67, 35},
-      {0, 48, 68},
-      {0, 1, 69},
-      {0, 70, 28},
-      {0, 27, 63},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 75, 60},
-      {0, 77, 76},
-      {0, 5, 51},
-      {0, 15, 78},
-      {0, 30, 79},
-      {0, 55, 80},
-      {0, 42, 81},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 86, 2},
-      {0, 19, 16},
-      {0, 87, 59},
-      {0, 62, 88},
-      {0, 90, 89},
-      {0, 22, 91},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 12, 102},
-      {0, 104, 103},
-      {0, 33, 105},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 65, 128},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
-      {0, 0, 0},
-      {13319433, 0, 0},
-      {15502752, 0, 0},
-      {162608772, 0, 0},
-      {171307615, 0, 0},
-      {296981500, 0, 0},
-      {354479447, 0, 0},
-      {413918748, 0, 0},
-      {443490822, 0, 0},
-      {487719832, 0, 0},
-      {593829839, 0, 0},
-      {615982737, 0, 0},
-      {703543228, 0, 0},
-      {810488476, 0, 0},
-      {870594305, 0, 0},
-      {875212982, 0, 0},
-      {959681532, 0, 0},
-      {1019457583, 0, 0},
-      {1203545131, 0, 0},
-      {1278448636, 0, 0},
-      {1325348861, 0, 0},
-      {1368383673, 0, 0},
-      {1400019344, 0, 0},
-      {1646147798, 0, 0},
-      {1679946323, 0, 0},
-      {1684282922, 0, 0},
-      {1747355813, 0, 0},
-      {1755648697, 0, 0},
-      {1793544760, 0, 0},
-      {1811839150, 0, 0},
-      {1901166356, 0, 0},
-      {1947620272, 0, 0},
-      {1992893964, 0, 0},
-      {2042001863, 0, 0},
-      {2096388952, 0, 0},
-      {2123388694, 0, 0},
-      {2128251367, 0, 0},
-      {2130747644, 0, 0},
-      {2135340676, 0, 0},
-      {2161102232, 0, 0},
-      {2443959748, 0, 0},
-      {2513230733, 0, 0},
-      {2557754096, 0, 0},
-      {2580096524, 0, 0},
-      {2589449658, 0, 0},
-      {2614879967, 0, 0},
-      {2698156268, 0, 0},
-      {2970183398, 0, 0},
-      {3002890475, 0, 0},
-      {3133016299, 0, 0},
-      {3142155593, 0, 0},
-      {3187066832, 0, 0},
-      {3266548732, 0, 0},
-      {3287039847, 0, 0},
-      {3357301402, 0, 0},
-      {3413713311, 0, 0},
-      {3434076295, 0, 0},
-      {3496407048, 0, 0},
-      {3504158761, 0, 0},
-      {3882634684, 0, 0},
-      {3929248764, 0, 0},
-      {3987079331, 0, 0},
-      {4076840151, 0, 0},
-      {4243119782, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 31, 8},
-      {0, 14, 56},
-      {0, 7, 12},
-      {0, 9, 30},
-      {0, 42, 36},
-      {0, 19, 11},
-      {0, 22, 40},
-      {0, 15, 3},
-      {0, 57, 26},
-      {0, 58, 61},
-      {0, 55, 51},
-      {0, 48, 34},
-      {0, 20, 1},
-      {0, 24, 23},
-      {0, 46, 35},
-      {0, 59, 49},
-      {0, 21, 63},
-      {0, 62, 44},
-      {0, 6, 50},
-      {0, 28, 18},
-      {0, 66, 65},
-      {0, 41, 32},
-      {0, 39, 54},
-      {0, 53, 67},
-      {0, 68, 37},
-      {0, 33, 69},
-      {0, 43, 70},
-      {0, 71, 38},
-      {0, 72, 27},
-      {0, 13, 47},
-      {0, 45, 73},
-      {0, 75, 74},
-      {0, 76, 5},
-      {0, 77, 17},
-      {0, 79, 78},
-      {0, 52, 80},
-      {0, 2, 81},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 4, 88},
-      {0, 16, 29},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-      {0, 60, 95},
-      {0, 97, 96},
-      {0, 98, 10},
-      {0, 25, 99},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 64, 126},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFMul, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(9, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {1951208733, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 4, 5},
-      {0, 3, 6},
-      {0, 7, 1},
-      {0, 2, 8},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(153, {
-      {0, 0, 0},
-      {10142671, 0, 0},
-      {27865391, 0, 0},
-      {29517006, 0, 0},
-      {41739659, 0, 0},
-      {97231530, 0, 0},
-      {171334650, 0, 0},
-      {200553094, 0, 0},
-      {257136089, 0, 0},
-      {294390719, 0, 0},
-      {375530199, 0, 0},
-      {380957745, 0, 0},
-      {388034151, 0, 0},
-      {455591063, 0, 0},
-      {462664429, 0, 0},
-      {491456522, 0, 0},
-      {502863753, 0, 0},
-      {626480004, 0, 0},
-      {643418617, 0, 0},
-      {651464351, 0, 0},
-      {701281393, 0, 0},
-      {744817486, 0, 0},
-      {783918780, 0, 0},
-      {862784766, 0, 0},
-      {930804377, 0, 0},
-      {952536201, 0, 0},
-      {955476870, 0, 0},
-      {1043738701, 0, 0},
-      {1047011733, 0, 0},
-      {1080545747, 0, 0},
-      {1137442027, 0, 0},
-      {1235468610, 0, 0},
-      {1412908157, 0, 0},
-      {1431749301, 0, 0},
-      {1434223270, 0, 0},
-      {1440646342, 0, 0},
-      {1508570930, 0, 0},
-      {1510422521, 0, 0},
-      {1548121999, 0, 0},
-      {1582841441, 0, 0},
-      {1612225949, 0, 0},
-      {1665981878, 0, 0},
-      {1680746207, 0, 0},
-      {1696076631, 0, 0},
-      {1702168830, 0, 0},
-      {1761469971, 0, 0},
-      {1799299383, 0, 0},
-      {1910240213, 0, 0},
-      {1917451875, 0, 0},
-      {1945006185, 0, 0},
-      {1998444837, 0, 0},
-      {2045285083, 0, 0},
-      {2217966239, 0, 0},
-      {2279273489, 0, 0},
-      {2289803479, 0, 0},
-      {2348676810, 0, 0},
-      {2353194283, 0, 0},
-      {2403632109, 0, 0},
-      {2409539315, 0, 0},
-      {2414984922, 0, 0},
-      {2477389837, 0, 0},
-      {2524531022, 0, 0},
-      {2573160348, 0, 0},
-      {2639720559, 0, 0},
-      {2773229577, 0, 0},
-      {2796513469, 0, 0},
-      {2881225774, 0, 0},
-      {2890570341, 0, 0},
-      {2952850186, 0, 0},
-      {3023287679, 0, 0},
-      {3118548424, 0, 0},
-      {3877813395, 0, 0},
-      {3931288033, 0, 0},
-      {3972309363, 0, 0},
-      {4117704995, 0, 0},
-      {4140081844, 0, 0},
-      {4258414038, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 74, 53},
-      {0, 58, 52},
-      {0, 65, 60},
-      {0, 41, 5},
-      {0, 1, 67},
-      {0, 24, 28},
-      {0, 27, 26},
-      {0, 55, 31},
-      {0, 36, 61},
-      {0, 13, 49},
-      {0, 56, 48},
-      {0, 16, 64},
-      {0, 76, 42},
-      {0, 45, 29},
-      {0, 23, 6},
-      {0, 72, 12},
-      {0, 35, 19},
-      {0, 20, 7},
-      {0, 21, 46},
-      {0, 71, 78},
-      {0, 80, 79},
-      {0, 47, 17},
-      {0, 81, 70},
-      {0, 34, 25},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 37, 86},
-      {0, 87, 73},
-      {0, 10, 4},
-      {0, 40, 30},
-      {0, 88, 57},
-      {0, 54, 89},
-      {0, 50, 90},
-      {0, 11, 91},
-      {0, 39, 15},
-      {0, 59, 44},
-      {0, 92, 66},
-      {0, 69, 93},
-      {0, 95, 94},
-      {0, 14, 96},
-      {0, 98, 97},
-      {0, 62, 51},
-      {0, 100, 99},
-      {0, 102, 101},
-      {0, 104, 103},
-      {0, 32, 43},
-      {0, 105, 38},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 22, 9},
-      {0, 33, 110},
-      {0, 2, 111},
-      {0, 112, 3},
-      {0, 114, 113},
-      {0, 116, 115},
-      {0, 68, 63},
-      {0, 118, 117},
-      {0, 120, 119},
-      {0, 121, 8},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 129, 128},
-      {0, 131, 130},
-      {0, 133, 132},
-      {0, 75, 18},
-      {0, 135, 134},
-      {0, 137, 136},
-      {0, 139, 138},
-      {0, 141, 140},
-      {0, 143, 142},
-      {0, 145, 144},
-      {0, 147, 146},
-      {0, 149, 148},
-      {0, 150, 77},
-      {0, 152, 151},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(131, {
-      {0, 0, 0},
-      {5908395, 0, 0},
-      {139011596, 0, 0},
-      {296981500, 0, 0},
-      {342615870, 0, 0},
-      {370232173, 0, 0},
-      {492958971, 0, 0},
-      {528662843, 0, 0},
-      {551924251, 0, 0},
-      {604894932, 0, 0},
-      {610429940, 0, 0},
-      {780957373, 0, 0},
-      {810488476, 0, 0},
-      {872544165, 0, 0},
-      {878733439, 0, 0},
-      {918849409, 0, 0},
-      {959681532, 0, 0},
-      {1013756921, 0, 0},
-      {1038982109, 0, 0},
-      {1081611718, 0, 0},
-      {1125913837, 0, 0},
-      {1209418480, 0, 0},
-      {1318081294, 0, 0},
-      {1367301635, 0, 0},
-      {1417425499, 0, 0},
-      {1625742020, 0, 0},
-      {1684282922, 0, 0},
-      {1746004874, 0, 0},
-      {1758287856, 0, 0},
-      {1777640493, 0, 0},
-      {2066323109, 0, 0},
-      {2094550054, 0, 0},
-      {2096388952, 0, 0},
-      {2144962711, 0, 0},
-      {2434845539, 0, 0},
-      {2480811229, 0, 0},
-      {2552825357, 0, 0},
-      {2636946065, 0, 0},
-      {2651956495, 0, 0},
-      {2669086217, 0, 0},
-      {2680819379, 0, 0},
-      {2709694527, 0, 0},
-      {2715304020, 0, 0},
-      {2790648021, 0, 0},
-      {2802261839, 0, 0},
-      {2806296851, 0, 0},
-      {2864543087, 0, 0},
-      {2952260510, 0, 0},
-      {2963184673, 0, 0},
-      {3091876332, 0, 0},
-      {3098991995, 0, 0},
-      {3131890669, 0, 0},
-      {3138977758, 0, 0},
-      {3198541202, 0, 0},
-      {3260579369, 0, 0},
-      {3263841912, 0, 0},
-      {3335250889, 0, 0},
-      {3345856521, 0, 0},
-      {3381478137, 0, 0},
-      {3489269251, 0, 0},
-      {3510242586, 0, 0},
-      {3820814597, 0, 0},
-      {3900859293, 0, 0},
-      {4041974454, 0, 0},
-      {4244540017, 0, 0},
-      {4265894873, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 15, 52},
-      {0, 20, 18},
-      {0, 39, 29},
-      {0, 9, 43},
-      {0, 22, 13},
-      {0, 46, 27},
-      {0, 51, 48},
-      {0, 19, 57},
-      {0, 34, 24},
-      {0, 64, 59},
-      {0, 5, 7},
-      {0, 38, 37},
-      {0, 45, 47},
-      {0, 2, 56},
-      {0, 67, 8},
-      {0, 17, 68},
-      {0, 69, 61},
-      {0, 70, 6},
-      {0, 55, 54},
-      {0, 72, 71},
-      {0, 4, 73},
-      {0, 74, 40},
-      {0, 30, 11},
-      {0, 42, 36},
-      {0, 75, 58},
-      {0, 31, 76},
-      {0, 1, 77},
-      {0, 44, 14},
-      {0, 78, 50},
-      {0, 79, 23},
-      {0, 26, 80},
-      {0, 81, 12},
-      {0, 83, 82},
-      {0, 84, 21},
-      {0, 32, 85},
-      {0, 87, 86},
-      {0, 35, 10},
-      {0, 88, 62},
-      {0, 90, 89},
-      {0, 41, 91},
-      {0, 92, 53},
-      {0, 93, 63},
-      {0, 95, 94},
-      {0, 33, 96},
-      {0, 98, 97},
-      {0, 99, 3},
-      {0, 100, 28},
-      {0, 101, 49},
-      {0, 102, 60},
-      {0, 104, 103},
-      {0, 106, 105},
-      {0, 108, 107},
-      {0, 110, 109},
-      {0, 65, 111},
-      {0, 25, 112},
-      {0, 114, 113},
-      {0, 116, 115},
-      {0, 117, 16},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 127, 126},
-      {0, 128, 66},
-      {0, 130, 129},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(95, {
-      {0, 0, 0},
-      {116093251, 0, 0},
-      {149720480, 0, 0},
-      {183103444, 0, 0},
-      {251209228, 0, 0},
-      {296981500, 0, 0},
-      {357505993, 0, 0},
-      {394654115, 0, 0},
-      {410274915, 0, 0},
-      {452208841, 0, 0},
-      {788046331, 0, 0},
-      {797934924, 0, 0},
-      {810488476, 0, 0},
-      {1144188012, 0, 0},
-      {1220127364, 0, 0},
-      {1321616112, 0, 0},
-      {1324351672, 0, 0},
-      {1348149915, 0, 0},
-      {1459457331, 0, 0},
-      {1465623797, 0, 0},
-      {1531216990, 0, 0},
-      {1543672828, 0, 0},
-      {1578775276, 0, 0},
-      {1738815671, 0, 0},
-      {1904128160, 0, 0},
-      {2071351379, 0, 0},
-      {2119793999, 0, 0},
-      {2274779301, 0, 0},
-      {2291766425, 0, 0},
-      {2357410109, 0, 0},
-      {2438466459, 0, 0},
-      {2496463830, 0, 0},
-      {2630220147, 0, 0},
-      {2682510803, 0, 0},
-      {3047649911, 0, 0},
-      {3085703811, 0, 0},
-      {3235459678, 0, 0},
-      {3261703164, 0, 0},
-      {3331487616, 0, 0},
-      {3462674048, 0, 0},
-      {3570219049, 0, 0},
-      {3585315836, 0, 0},
-      {3602108619, 0, 0},
-      {3724004880, 0, 0},
-      {3931641900, 0, 0},
-      {3955205564, 0, 0},
-      {4073492988, 0, 0},
-      {4127308103, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 24, 37},
-      {0, 13, 38},
-      {0, 17, 39},
-      {0, 35, 23},
-      {0, 18, 36},
-      {0, 46, 19},
-      {0, 20, 33},
-      {0, 47, 6},
-      {0, 1, 45},
-      {0, 3, 27},
-      {0, 8, 49},
-      {0, 50, 29},
-      {0, 10, 51},
-      {0, 43, 31},
-      {0, 53, 52},
-      {0, 54, 26},
-      {0, 7, 55},
-      {0, 56, 32},
-      {0, 57, 41},
-      {0, 59, 58},
-      {0, 61, 60},
-      {0, 63, 62},
-      {0, 64, 25},
-      {0, 2, 34},
-      {0, 65, 14},
-      {0, 67, 66},
-      {0, 12, 21},
-      {0, 9, 68},
-      {0, 69, 16},
-      {0, 71, 70},
-      {0, 72, 44},
-      {0, 11, 73},
-      {0, 74, 30},
-      {0, 4, 75},
-      {0, 28, 15},
-      {0, 76, 42},
-      {0, 5, 77},
-      {0, 78, 40},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 22, 83},
-      {0, 85, 84},
-      {0, 86, 48},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 92, 91},
-      {0, 94, 93},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpFDiv, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(7, {
-      {0, 0, 0},
-      {679771963, 0, 0},
-      {2320303498, 0, 0},
-      {3334207724, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 3, 4},
-      {0, 2, 5},
-      {0, 1, 6},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(121, {
-      {0, 0, 0},
-      {14113753, 0, 0},
-      {102358168, 0, 0},
-      {179458548, 0, 0},
-      {330388453, 0, 0},
-      {386525753, 0, 0},
-      {470277359, 0, 0},
-      {497658126, 0, 0},
-      {508007510, 0, 0},
-      {815034111, 0, 0},
-      {826214242, 0, 0},
-      {849867303, 0, 0},
-      {885645401, 0, 0},
-      {939415664, 0, 0},
-      {968885186, 0, 0},
-      {1105835505, 0, 0},
-      {1159301677, 0, 0},
-      {1461897718, 0, 0},
-      {1482251215, 0, 0},
-      {1486206763, 0, 0},
-      {1527762373, 0, 0},
-      {1558990974, 0, 0},
-      {1618754372, 0, 0},
-      {1669959736, 0, 0},
-      {1752686878, 0, 0},
-      {2004567202, 0, 0},
-      {2055637638, 0, 0},
-      {2113506324, 0, 0},
-      {2154320787, 0, 0},
-      {2162274327, 0, 0},
-      {2306141594, 0, 0},
-      {2345566651, 0, 0},
-      {2457690657, 0, 0},
-      {2473053808, 0, 0},
-      {2500422644, 0, 0},
-      {2504802016, 0, 0},
-      {2506771164, 0, 0},
-      {2793529873, 0, 0},
-      {2801333547, 0, 0},
-      {2879050471, 0, 0},
-      {3032677281, 0, 0},
-      {3045470312, 0, 0},
-      {3181546731, 0, 0},
-      {3240977890, 0, 0},
-      {3262572726, 0, 0},
-      {3307100165, 0, 0},
-      {3425841570, 0, 0},
-      {3560552546, 0, 0},
-      {3641833815, 0, 0},
-      {3652695478, 0, 0},
-      {3782362128, 0, 0},
-      {3797961332, 0, 0},
-      {3837583704, 0, 0},
-      {3886529747, 0, 0},
-      {3907920335, 0, 0},
-      {4043078107, 0, 0},
-      {4044928561, 0, 0},
-      {4069720347, 0, 0},
-      {4180570743, 0, 0},
-      {4245743275, 0, 0},
-      {4285201458, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 44, 28},
-      {0, 13, 45},
-      {0, 19, 15},
-      {0, 32, 31},
-      {0, 43, 42},
-      {0, 16, 52},
-      {0, 33, 22},
-      {0, 57, 55},
-      {0, 24, 21},
-      {0, 2, 59},
-      {0, 10, 3},
-      {0, 18, 12},
-      {0, 41, 39},
-      {0, 60, 46},
-      {0, 4, 25},
-      {0, 58, 49},
-      {0, 14, 1},
-      {0, 27, 17},
-      {0, 50, 36},
-      {0, 23, 54},
-      {0, 5, 30},
-      {0, 11, 7},
-      {0, 38, 29},
-      {0, 37, 8},
-      {0, 48, 56},
-      {0, 20, 6},
-      {0, 34, 26},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 67, 66},
-      {0, 69, 68},
-      {0, 71, 70},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 9, 76},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 84, 83},
-      {0, 40, 35},
-      {0, 85, 47},
-      {0, 86, 51},
-      {0, 88, 87},
-      {0, 90, 89},
-      {0, 53, 91},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 61, 120},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(127, {
-      {0, 0, 0},
-      {100979271, 0, 0},
-      {269576093, 0, 0},
-      {314809953, 0, 0},
-      {354479447, 0, 0},
-      {497658126, 0, 0},
-      {882718761, 0, 0},
-      {968885186, 0, 0},
-      {973908139, 0, 0},
-      {1019457583, 0, 0},
-      {1191015885, 0, 0},
-      {1266262705, 0, 0},
-      {1310404265, 0, 0},
-      {1325348861, 0, 0},
-      {1367301635, 0, 0},
-      {1368383673, 0, 0},
-      {1570165302, 0, 0},
-      {1618544981, 0, 0},
-      {1646147798, 0, 0},
-      {1674464100, 0, 0},
-      {1679946323, 0, 0},
-      {1686512349, 0, 0},
-      {1766401548, 0, 0},
-      {1774052499, 0, 0},
-      {1788301425, 0, 0},
-      {2023008475, 0, 0},
-      {2055836767, 0, 0},
-      {2096388952, 0, 0},
-      {2123388694, 0, 0},
-      {2129301998, 0, 0},
-      {2212501241, 0, 0},
-      {2274226560, 0, 0},
-      {2362972044, 0, 0},
-      {2378763734, 0, 0},
-      {2506771164, 0, 0},
-      {2558655180, 0, 0},
-      {2622612602, 0, 0},
-      {2660843182, 0, 0},
-      {2698156268, 0, 0},
-      {2801333547, 0, 0},
-      {2850246066, 0, 0},
-      {2895151306, 0, 0},
-      {2970183398, 0, 0},
-      {2986830770, 0, 0},
-      {3001444829, 0, 0},
-      {3133016299, 0, 0},
-      {3152745753, 0, 0},
-      {3187066832, 0, 0},
-      {3261122899, 0, 0},
-      {3496407048, 0, 0},
-      {3513669836, 0, 0},
-      {3536390697, 0, 0},
-      {3570411982, 0, 0},
-      {3653838348, 0, 0},
-      {3713290482, 0, 0},
-      {3858973601, 0, 0},
-      {3873587660, 0, 0},
-      {3877583949, 0, 0},
-      {3882634684, 0, 0},
-      {3907920335, 0, 0},
-      {3997432565, 0, 0},
-      {4169226615, 0, 0},
-      {4219766939, 0, 0},
-      {4243119782, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 25, 12},
-      {0, 41, 29},
-      {0, 56, 44},
-      {0, 1, 3},
-      {0, 48, 24},
-      {0, 33, 60},
-      {0, 8, 50},
-      {0, 35, 21},
-      {0, 11, 7},
-      {0, 34, 23},
-      {0, 59, 57},
-      {0, 10, 62},
-      {0, 40, 2},
-      {0, 5, 49},
-      {0, 39, 17},
-      {0, 9, 61},
-      {0, 30, 6},
-      {0, 19, 46},
-      {0, 53, 54},
-      {0, 31, 52},
-      {0, 55, 43},
-      {0, 66, 65},
-      {0, 16, 67},
-      {0, 51, 68},
-      {0, 70, 69},
-      {0, 26, 36},
-      {0, 72, 71},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 80, 79},
-      {0, 82, 81},
-      {0, 37, 83},
-      {0, 85, 84},
-      {0, 13, 86},
-      {0, 20, 18},
-      {0, 38, 28},
-      {0, 58, 45},
-      {0, 87, 63},
-      {0, 15, 88},
-      {0, 32, 22},
-      {0, 89, 4},
-      {0, 90, 14},
-      {0, 91, 42},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 115, 114},
-      {0, 27, 47},
-      {0, 117, 116},
-      {0, 119, 118},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 125, 124},
-      {0, 126, 64},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(137, {
-      {0, 0, 0},
-      {11698369, 0, 0},
-      {146392076, 0, 0},
-      {151810803, 0, 0},
-      {223800276, 0, 0},
-      {227103506, 0, 0},
-      {253329281, 0, 0},
-      {346929928, 0, 0},
-      {461040879, 0, 0},
-      {629859130, 0, 0},
-      {680157484, 0, 0},
-      {783918780, 0, 0},
-      {810488476, 0, 0},
-      {824323032, 0, 0},
-      {870594305, 0, 0},
-      {959681532, 0, 0},
-      {975807626, 0, 0},
-      {1081642571, 0, 0},
-      {1084574846, 0, 0},
-      {1094817798, 0, 0},
-      {1141965917, 0, 0},
-      {1164137269, 0, 0},
-      {1166917451, 0, 0},
-      {1204787336, 0, 0},
-      {1232501371, 0, 0},
-      {1318479490, 0, 0},
-      {1369818198, 0, 0},
-      {1372785527, 0, 0},
-      {1526654696, 0, 0},
-      {1543672828, 0, 0},
-      {1548121999, 0, 0},
-      {1635292159, 0, 0},
-      {1641070431, 0, 0},
-      {1684282922, 0, 0},
-      {1767704813, 0, 0},
-      {1781765116, 0, 0},
-      {1838763297, 0, 0},
-      {1901166356, 0, 0},
-      {1904846533, 0, 0},
-      {2011183308, 0, 0},
-      {2032069771, 0, 0},
-      {2071351379, 0, 0},
-      {2087004702, 0, 0},
-      {2244928358, 0, 0},
-      {2314864456, 0, 0},
-      {2374216296, 0, 0},
-      {2394332122, 0, 0},
-      {2443610186, 0, 0},
-      {2524697596, 0, 0},
-      {2526961521, 0, 0},
-      {2568098594, 0, 0},
-      {2807907995, 0, 0},
-      {3103302036, 0, 0},
-      {3117071189, 0, 0},
-      {3188115516, 0, 0},
-      {3417584874, 0, 0},
-      {3554463148, 0, 0},
-      {3561482820, 0, 0},
-      {3691770462, 0, 0},
-      {3729929345, 0, 0},
-      {3733675151, 0, 0},
-      {3831290364, 0, 0},
-      {3866493821, 0, 0},
-      {3929248764, 0, 0},
-      {4060703604, 0, 0},
-      {4092487128, 0, 0},
-      {4167600590, 0, 0},
-      {4214779116, 0, 0},
-      {4248015868, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 36, 13},
-      {0, 49, 60},
-      {0, 51, 9},
-      {0, 3, 62},
-      {0, 67, 41},
-      {0, 4, 31},
-      {0, 66, 5},
-      {0, 55, 32},
-      {0, 2, 1},
-      {0, 30, 16},
-      {0, 7, 38},
-      {0, 19, 10},
-      {0, 34, 20},
-      {0, 45, 46},
-      {0, 22, 11},
-      {0, 25, 23},
-      {0, 40, 39},
-      {0, 21, 57},
-      {0, 6, 35},
-      {0, 61, 8},
-      {0, 52, 26},
-      {0, 70, 59},
-      {0, 71, 14},
-      {0, 68, 47},
-      {0, 73, 72},
-      {0, 29, 74},
-      {0, 76, 75},
-      {0, 77, 17},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 82, 18},
-      {0, 83, 42},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 27, 37},
-      {0, 53, 43},
-      {0, 89, 88},
-      {0, 64, 54},
-      {0, 90, 65},
-      {0, 92, 91},
-      {0, 58, 93},
-      {0, 56, 48},
-      {0, 94, 28},
-      {0, 96, 95},
-      {0, 98, 97},
-      {0, 44, 99},
-      {0, 101, 100},
-      {0, 15, 12},
-      {0, 103, 102},
-      {0, 104, 33},
-      {0, 106, 105},
-      {0, 108, 107},
-      {0, 24, 109},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 114, 50},
-      {0, 116, 115},
-      {0, 118, 117},
-      {0, 120, 119},
-      {0, 122, 121},
-      {0, 124, 123},
-      {0, 126, 125},
-      {0, 128, 127},
-      {0, 129, 63},
-      {0, 131, 130},
-      {0, 133, 132},
-      {0, 135, 134},
-      {0, 136, 69},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpVectorTimesScalar, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1951208733, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(97, {
-      {0, 0, 0},
-      {78001013, 0, 0},
-      {170690025, 0, 0},
-      {206688607, 0, 0},
-      {443490822, 0, 0},
-      {461476226, 0, 0},
-      {537830163, 0, 0},
-      {669982125, 0, 0},
-      {790502615, 0, 0},
-      {805072272, 0, 0},
-      {1173092699, 0, 0},
-      {1220643281, 0, 0},
-      {1448448666, 0, 0},
-      {1466804584, 0, 0},
-      {1473411044, 0, 0},
-      {1515695460, 0, 0},
-      {1587730355, 0, 0},
-      {1625742020, 0, 0},
-      {2071351379, 0, 0},
-      {2250055803, 0, 0},
-      {2291766425, 0, 0},
-      {2416108131, 0, 0},
-      {2427834344, 0, 0},
-      {2436009347, 0, 0},
-      {2455417440, 0, 0},
-      {2480811229, 0, 0},
-      {2654325647, 0, 0},
-      {2919796598, 0, 0},
-      {3047649911, 0, 0},
-      {3088511797, 0, 0},
-      {3104643263, 0, 0},
-      {3198541202, 0, 0},
-      {3204986803, 0, 0},
-      {3272233597, 0, 0},
-      {3383007207, 0, 0},
-      {3602108619, 0, 0},
-      {3622349409, 0, 0},
-      {3714664910, 0, 0},
-      {3717942504, 0, 0},
-      {3732000233, 0, 0},
-      {3759072440, 0, 0},
-      {3765247327, 0, 0},
-      {3805423332, 0, 0},
-      {3829325073, 0, 0},
-      {3866493821, 0, 0},
-      {4058280485, 0, 0},
-      {4061558677, 0, 0},
-      {4148979936, 0, 0},
-      {4155586396, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 13, 38},
-      {0, 39, 14},
-      {0, 44, 9},
-      {0, 48, 47},
-      {0, 23, 15},
-      {0, 33, 25},
-      {0, 1, 42},
-      {0, 5, 46},
-      {0, 31, 3},
-      {0, 36, 28},
-      {0, 16, 12},
-      {0, 32, 22},
-      {0, 41, 21},
-      {0, 6, 50},
-      {0, 51, 29},
-      {0, 45, 34},
-      {0, 37, 8},
-      {0, 19, 52},
-      {0, 11, 4},
-      {0, 43, 40},
-      {0, 27, 53},
-      {0, 54, 10},
-      {0, 24, 55},
-      {0, 57, 56},
-      {0, 58, 26},
-      {0, 2, 59},
-      {0, 61, 60},
-      {0, 63, 62},
-      {0, 65, 64},
-      {0, 20, 66},
-      {0, 30, 35},
-      {0, 67, 17},
-      {0, 68, 7},
-      {0, 70, 69},
-      {0, 71, 18},
-      {0, 73, 72},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 79, 78},
-      {0, 81, 80},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 87, 86},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 95, 94},
-      {0, 49, 96},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 1), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(117, {
-      {0, 0, 0},
-      {50385656, 0, 0},
-      {181902171, 0, 0},
-      {560078433, 0, 0},
-      {615982737, 0, 0},
-      {674428451, 0, 0},
-      {837715723, 0, 0},
-      {886972033, 0, 0},
-      {900101778, 0, 0},
-      {983299427, 0, 0},
-      {1237148906, 0, 0},
-      {1364157225, 0, 0},
-      {1367301635, 0, 0},
-      {1380160211, 0, 0},
-      {1451831482, 0, 0},
-      {1499923635, 0, 0},
-      {1570165302, 0, 0},
-      {1735295265, 0, 0},
-      {1766401548, 0, 0},
-      {1796311149, 0, 0},
-      {1826456251, 0, 0},
-      {1839669171, 0, 0},
-      {2012838864, 0, 0},
-      {2024071551, 0, 0},
-      {2096388952, 0, 0},
-      {2161102232, 0, 0},
-      {2197874825, 0, 0},
-      {2279700640, 0, 0},
-      {2289183712, 0, 0},
-      {2351620600, 0, 0},
-      {2362972044, 0, 0},
-      {2472176885, 0, 0},
-      {2477434291, 0, 0},
-      {2530899578, 0, 0},
-      {2531826164, 0, 0},
-      {2558133383, 0, 0},
-      {2589449658, 0, 0},
-      {2621255555, 0, 0},
-      {2622612602, 0, 0},
-      {2872580757, 0, 0},
-      {2881302403, 0, 0},
-      {2891091137, 0, 0},
-      {2923708820, 0, 0},
-      {2936040203, 0, 0},
-      {2970183398, 0, 0},
-      {3187066832, 0, 0},
-      {3224952074, 0, 0},
-      {3244383472, 0, 0},
-      {3261122899, 0, 0},
-      {3362830643, 0, 0},
-      {3538158875, 0, 0},
-      {3635542517, 0, 0},
-      {3682213068, 0, 0},
-      {3721902098, 0, 0},
-      {3826846522, 0, 0},
-      {3877583949, 0, 0},
-      {3997432565, 0, 0},
-      {4093615095, 0, 0},
-      {4106828015, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 52, 28},
-      {0, 33, 20},
-      {0, 46, 57},
-      {0, 47, 54},
-      {0, 21, 17},
-      {0, 31, 58},
-      {0, 12, 53},
-      {0, 29, 3},
-      {0, 35, 34},
-      {0, 48, 41},
-      {0, 8, 5},
-      {0, 7, 55},
-      {0, 37, 32},
-      {0, 60, 38},
-      {0, 61, 16},
-      {0, 14, 62},
-      {0, 23, 63},
-      {0, 13, 19},
-      {0, 64, 9},
-      {0, 65, 39},
-      {0, 2, 66},
-      {0, 67, 42},
-      {0, 69, 68},
-      {0, 25, 70},
-      {0, 1, 49},
-      {0, 6, 71},
-      {0, 72, 15},
-      {0, 73, 11},
-      {0, 75, 74},
-      {0, 77, 76},
-      {0, 4, 78},
-      {0, 56, 50},
-      {0, 80, 79},
-      {0, 10, 81},
-      {0, 83, 82},
-      {0, 85, 84},
-      {0, 86, 27},
-      {0, 43, 40},
-      {0, 88, 87},
-      {0, 44, 24},
-      {0, 30, 89},
-      {0, 51, 36},
-      {0, 45, 90},
-      {0, 18, 91},
-      {0, 93, 92},
-      {0, 22, 94},
-      {0, 26, 95},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 101, 100},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 113, 112},
-      {0, 59, 114},
-      {0, 116, 115},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 2), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(179, {
-      {0, 0, 0},
-      {27177503, 0, 0},
-      {50385656, 0, 0},
-      {129748122, 0, 0},
-      {139011596, 0, 0},
-      {162608772, 0, 0},
-      {181902171, 0, 0},
-      {225200779, 0, 0},
-      {342159236, 0, 0},
-      {386293029, 0, 0},
-      {429023543, 0, 0},
-      {443558693, 0, 0},
-      {504514034, 0, 0},
-      {615982737, 0, 0},
-      {669812542, 0, 0},
-      {674428451, 0, 0},
-      {837715723, 0, 0},
-      {861753115, 0, 0},
-      {875212982, 0, 0},
-      {876867882, 0, 0},
-      {899320334, 0, 0},
-      {900101778, 0, 0},
-      {938517572, 0, 0},
-      {1347339159, 0, 0},
-      {1356063462, 0, 0},
-      {1373856501, 0, 0},
-      {1376656865, 0, 0},
-      {1451831482, 0, 0},
-      {1522979646, 0, 0},
-      {1548491889, 0, 0},
-      {1570165302, 0, 0},
-      {1735295265, 0, 0},
-      {1747355813, 0, 0},
-      {1766401548, 0, 0},
-      {1871105284, 0, 0},
-      {1918742169, 0, 0},
-      {1922045399, 0, 0},
-      {1978689945, 0, 0},
-      {2024071551, 0, 0},
-      {2059975069, 0, 0},
-      {2076833303, 0, 0},
-      {2096388952, 0, 0},
-      {2181030375, 0, 0},
-      {2197874825, 0, 0},
-      {2362972044, 0, 0},
-      {2414725163, 0, 0},
-      {2517964682, 0, 0},
-      {2564745684, 0, 0},
-      {2577387676, 0, 0},
-      {2589449658, 0, 0},
-      {2604242419, 0, 0},
-      {2683080096, 0, 0},
-      {2696349144, 0, 0},
-      {2763960513, 0, 0},
-      {2817823941, 0, 0},
-      {2852854788, 0, 0},
-      {2891091137, 0, 0},
-      {2919626325, 0, 0},
-      {2923708820, 0, 0},
-      {2936040203, 0, 0},
-      {2963744582, 0, 0},
-      {2970183398, 0, 0},
-      {2984459037, 0, 0},
-      {2996594997, 0, 0},
-      {3015046341, 0, 0},
-      {3055195668, 0, 0},
-      {3127329373, 0, 0},
-      {3187066832, 0, 0},
-      {3193597927, 0, 0},
-      {3200890815, 0, 0},
-      {3224258475, 0, 0},
-      {3224480461, 0, 0},
-      {3261122899, 0, 0},
-      {3609540589, 0, 0},
-      {3619404941, 0, 0},
-      {3619626927, 0, 0},
-      {3727034815, 0, 0},
-      {3742724777, 0, 0},
-      {3742946763, 0, 0},
-      {3836179806, 0, 0},
-      {3913885196, 0, 0},
-      {3927338499, 0, 0},
-      {3927466635, 0, 0},
-      {3997432565, 0, 0},
-      {3999472204, 0, 0},
-      {4010499223, 0, 0},
-      {4032662899, 0, 0},
-      {4110915453, 0, 0},
-      {4145966869, 0, 0},
-      {4228303141, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 23, 87},
-      {0, 9, 28},
-      {0, 42, 17},
-      {0, 74, 70},
-      {0, 86, 77},
-      {0, 18, 5},
-      {0, 31, 32},
-      {0, 34, 3},
-      {0, 38, 68},
-      {0, 50, 29},
-      {0, 72, 62},
-      {0, 21, 15},
-      {0, 14, 54},
-      {0, 56, 22},
-      {0, 48, 88},
-      {0, 2, 76},
-      {0, 6, 47},
-      {0, 26, 79},
-      {0, 65, 12},
-      {0, 37, 81},
-      {0, 91, 60},
-      {0, 30, 92},
-      {0, 25, 7},
-      {0, 45, 40},
-      {0, 66, 52},
-      {0, 71, 69},
-      {0, 78, 75},
-      {0, 84, 82},
-      {0, 94, 93},
-      {0, 27, 95},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 100, 39},
-      {0, 55, 101},
-      {0, 58, 102},
-      {0, 89, 103},
-      {0, 35, 11},
-      {0, 104, 36},
-      {0, 53, 10},
-      {0, 1, 64},
-      {0, 73, 20},
-      {0, 105, 13},
-      {0, 107, 106},
-      {0, 8, 16},
-      {0, 24, 19},
-      {0, 85, 63},
-      {0, 109, 108},
-      {0, 111, 110},
-      {0, 4, 112},
-      {0, 114, 113},
-      {0, 116, 115},
-      {0, 118, 117},
-      {0, 83, 119},
-      {0, 121, 120},
-      {0, 123, 122},
-      {0, 49, 44},
-      {0, 124, 57},
-      {0, 125, 59},
-      {0, 126, 67},
-      {0, 128, 127},
-      {0, 130, 129},
-      {0, 132, 131},
-      {0, 134, 133},
-      {0, 135, 51},
-      {0, 137, 136},
-      {0, 138, 61},
-      {0, 43, 41},
-      {0, 140, 139},
-      {0, 142, 141},
-      {0, 144, 143},
-      {0, 146, 145},
-      {0, 148, 147},
-      {0, 149, 33},
-      {0, 80, 150},
-      {0, 152, 151},
-      {0, 154, 153},
-      {0, 156, 155},
-      {0, 158, 157},
-      {0, 160, 159},
-      {0, 162, 161},
-      {0, 164, 163},
-      {0, 166, 165},
-      {0, 168, 167},
-      {0, 46, 169},
-      {0, 171, 170},
-      {0, 90, 172},
-      {0, 174, 173},
-      {0, 176, 175},
-      {0, 178, 177},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpDot, 3), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1036475267, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpLabel, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(3, {
-      {0, 0, 0},
-      {1036475267, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 1, 2},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpBranch, 0), std::move(codec));
-  }
-
-  {
-    std::unique_ptr<HuffmanCodec<uint64_t>> codec(new HuffmanCodec<uint64_t>(119, {
-      {0, 0, 0},
-      {57149555, 0, 0},
-      {139011596, 0, 0},
-      {255835594, 0, 0},
-      {330249537, 0, 0},
-      {388686774, 0, 0},
-      {508217552, 0, 0},
-      {550831114, 0, 0},
-      {559246409, 0, 0},
-      {599185303, 0, 0},
-      {649208064, 0, 0},
-      {679061455, 0, 0},
-      {810488476, 0, 0},
-      {951841533, 0, 0},
-      {1008886329, 0, 0},
-      {1022544883, 0, 0},
-      {1215030156, 0, 0},
-      {1305703280, 0, 0},
-      {1367301635, 0, 0},
-      {1453447304, 0, 0},
-      {1487177499, 0, 0},
-      {1603937321, 0, 0},
-      {1617826947, 0, 0},
-      {1643868273, 0, 0},
-      {1672607981, 0, 0},
-      {1681941034, 0, 0},
-      {1755165354, 0, 0},
-      {1781864804, 0, 0},
-      {1795715718, 0, 0},
-      {1977038330, 0, 0},
-      {2096388952, 0, 0},
-      {2204920111, 0, 0},
-      {2244470522, 0, 0},
-      {2330636993, 0, 0},
-      {2400601988, 0, 0},
-      {2424848261, 0, 0},
-      {2603020391, 0, 0},
-      {2622612602, 0, 0},
-      {2645135839, 0, 0},
-      {2660843182, 0, 0},
-      {2708915136, 0, 0},
-      {2724166585, 0, 0},
-      {2728667725, 0, 0},
-      {2890638791, 0, 0},
-      {2901034693, 0, 0},
-      {2941648648, 0, 0},
-      {2970183398, 0, 0},
-      {2998120306, 0, 0},
-      {3123244280, 0, 0},
-      {3187066832, 0, 0},
-      {3209399506, 0, 0},
-      {3230260738, 0, 0},
-      {3344189994, 0, 0},
-      {3345707173, 0, 0},
-      {3367298820, 0, 0},
-      {3397078357, 0, 0},
-      {3569736966, 0, 0},
-      {3816961131, 0, 0},
-      {4091670162, 0, 0},
-      {4237497041, 0, 0},
-      {1111111111111111111, 0, 0},
-      {0, 17, 44},
-      {0, 25, 20},
-      {0, 29, 34},
-      {0, 18, 2},
-      {0, 54, 49},
-      {0, 28, 7},
-      {0, 47, 52},
-      {0, 23, 56},
-      {0, 55, 26},
-      {0, 24, 61},
-      {0, 13, 62},
-      {0, 63, 45},
-      {0, 27, 15},
-      {0, 64, 8},
-      {0, 65, 59},
-      {0, 35, 22},
-      {0, 53, 38},
-      {0, 58, 51},
-      {0, 11, 66},
-      {0, 10, 3},
-      {0, 46, 67},
-      {0, 69, 68},
-      {0, 1, 50},
-      {0, 42, 19},
-      {0, 70, 6},
-      {0, 31, 71},
-      {0, 16, 72},
-      {0, 74, 73},
-      {0, 76, 75},
-      {0, 78, 77},
-      {0, 79, 4},
-      {0, 5, 37},
-      {0, 14, 36},
-      {0, 80, 57},
-      {0, 81, 48},
-      {0, 83, 82},
-      {0, 39, 84},
-      {0, 86, 85},
-      {0, 40, 87},
-      {0, 89, 88},
-      {0, 91, 90},
-      {0, 93, 92},
-      {0, 21, 9},
-      {0, 41, 32},
-      {0, 12, 43},
-      {0, 95, 94},
-      {0, 97, 96},
-      {0, 99, 98},
-      {0, 100, 33},
-      {0, 60, 101},
-      {0, 103, 102},
-      {0, 105, 104},
-      {0, 107, 106},
-      {0, 109, 108},
-      {0, 110, 30},
-      {0, 112, 111},
-      {0, 114, 113},
-      {0, 116, 115},
-      {0, 118, 117},
-    }));
-
-    codecs.emplace(std::pair<uint32_t, uint32_t>(SpvOpReturnValue, 0), std::move(codec));
-  }
-
-  return codecs;
-}
-
-std::unordered_set<uint32_t> GetDescriptorsWithCodingScheme() {
-  std::unordered_set<uint32_t> descriptors_with_coding_scheme = {
-    3816961131,
-    3569736966,
-    3397078357,
-    3344189994,
-    3230260738,
-    2941648648,
-    2901034693,
-    2728667725,
-    2400601988,
-    1795715718,
-    1681941034,
-    1487177499,
-    1453447304,
-    679061455,
-    649208064,
-    559246409,
-    388686774,
-    4228303141,
-    4110915453,
-    4010499223,
-    3927466635,
-    3927338499,
-    3836179806,
-    3742724777,
-    3619404941,
-    3224480461,
-    3224258475,
-    3200890815,
-    3742946763,
-    3193597927,
-    2604242419,
-    2577387676,
-    2181030375,
-    1376656865,
-    1347339159,
-    938517572,
-    876867882,
-    429023543,
-    129748122,
-    4106828015,
-    4093615095,
-    3826846522,
-    3721902098,
-    3244383472,
-    2891091137,
-    2872580757,
-    2558133383,
-    2477434291,
-    1839669171,
-    2059975069,
-    1735295265,
-    1364157225,
-    1237148906,
-    886972033,
-    674428451,
-    4148979936,
-    3805423332,
-    3732000233,
-    3717942504,
-    3714664910,
-    3622349409,
-    3272233597,
-    3204986803,
-    3088511797,
-    1672607981,
-    2416108131,
-    2250055803,
-    1796311149,
-    1515695460,
-    537830163,
-    461476226,
-    206688607,
-    78001013,
-    3866493821,
-    3417584874,
-    3188115516,
-    2526961521,
-    2443610186,
-    2394332122,
-    2374216296,
-    2032069771,
-    2011183308,
-    1904846533,
-    1641070431,
-    1635292159,
-    1372785527,
-    1369818198,
-    1204787336,
-    1826456251,
-    1164137269,
-    1081642571,
-    629859130,
-    253329281,
-    227103506,
-    11698369,
-    4219766939,
-    4169226615,
-    3997432565,
-    3873587660,
-    3513669836,
-    3261122899,
-    2129301998,
-    1774052499,
-    1266262705,
-    4285201458,
-    4245743275,
-    3907920335,
-    3837583704,
-    3641833815,
-    3307100165,
-    1232501371,
-    3262572726,
-    3045470312,
-    2879050471,
-    2801333547,
-    2506771164,
-    2504802016,
-    2500422644,
-    2473053808,
-    2457690657,
-    2345566651,
-    2306141594,
-    2154320787,
-    2055637638,
-    1527762373,
-    1486206763,
-    1159301677,
-    1105835505,
-    968885186,
-    885645401,
-    849867303,
-    815034111,
-    497658126,
-    386525753,
-    179458548,
-    102358168,
-    4127308103,
-    4073492988,
-    1473411044,
-    805072272,
-    3724004880,
-    3602108619,
-    3585315836,
-    3331487616,
-    3261703164,
-    3235459678,
-    3085703811,
-    3047649911,
-    2357410109,
-    2291766425,
-    2071351379,
-    1904128160,
-    1738815671,
-    1531216990,
-    1465623797,
-    1324351672,
-    1220127364,
-    1144188012,
-    183103444,
-    116093251,
-    3900859293,
-    3345856521,
-    3691770462,
-    3263841912,
-    3198541202,
-    3098991995,
-    3682213068,
-    2963184673,
-    2864543087,
-    2802261839,
-    2790648021,
-    900101778,
-    2715304020,
-    100979271,
-    2709694527,
-    2669086217,
-    2531826164,
-    2651956495,
-    2552825357,
-    2480811229,
-    3138977758,
-    2434845539,
-    2066323109,
-    1777640493,
-    1758287856,
-    1746004874,
-    3945482286,
-    3932146199,
-    3129573055,
-    3126269825,
-    3716914380,
-    985750227,
-    1543672828,
-    3189039115,
-    1839499483,
-    2696349144,
-    1536350567,
-    3971481069,
-    3001444829,
-    4028622909,
-    215293834,
-    213642219,
-    153085016,
-    1189681639,
-    165054168,
-    29517006,
-    2614879967,
-    27865391,
-    1649426421,
-    4239834800,
-    1947620272,
-    28782128,
-    3207966516,
-    3713290482,
-    2042001863,
-    2724166585,
-    2356768706,
-    1793544760,
-    4092654294,
-    2157103435,
-    2087004702,
-    2043873558,
-    27177503,
-    1033363654,
-    4214779116,
-    408465899,
-    451264926,
-    2377112119,
-    1182296898,
-    760554870,
-    3566035349,
-    2630220147,
-    4192247221,
-    1572088444,
-    3538592682,
-    769422756,
-    1674803691,
-    630964591,
-    3458449569,
-    565334834,
-    137840602,
-    3955205564,
-    2009007457,
-    1258105452,
-    333554713,
-    3923810593,
-    126463145,
-    3445109809,
-    2966409025,
-    2849215484,
-    1910240213,
-    3131890669,
-    586244865,
-    2320303498,
-    3116932970,
-    1317265040,
-    2812498065,
-    1466938734,
-    4064212479,
-    2613179511,
-    2095546797,
-    1671139745,
-    2568512089,
-    3695940604,
-    1119069977,
-    215027449,
-    4123141705,
-    3495546641,
-    1978689945,
-    3202324433,
-    3783543823,
-    2674422363,
-    1352628475,
-    1290956281,
-    1894417995,
-    740921498,
-    4211577142,
-    1033081852,
-    3884846406,
-    3253403867,
-    2790624748,
-    2538917932,
-    2144962711,
-    3323202731,
-    4290024976,
-    2564745684,
-    2963744582,
-    2443959748,
-    354479447,
-    750870327,
-    1918481917,
-    4032662899,
-    3587381650,
-    2414725163,
-    1081611718,
-    1625742020,
-    2308565678,
-    1871105284,
-    2807907995,
-    2121980967,
-    1054641568,
-    413918748,
-    1917336504,
-    1816558243,
-    4130950286,
-    1522979646,
-    1669959736,
-    1320550031,
-    3104643263,
-    3823959661,
-    3525913657,
-    3584683259,
-    2918750759,
-    3536390697,
-    94303122,
-    3296691317,
-    801484894,
-    2496463830,
-    3266028549,
-    3085157904,
-    973908139,
-    3787909072,
-    3107413701,
-    2378763734,
-    920604853,
-    2516325050,
-    1838993983,
-    1603937321,
-    3183924418,
-    1945006185,
-    3982311384,
-    2682510803,
-    680388473,
-    979993429,
-    2405770322,
-    461040879,
-    2817579280,
-    14113753,
-    2894979602,
-    168339452,
-    951841533,
-    4154758669,
-    2637132451,
-    3877583949,
-    1949856502,
-    922996215,
-    3941049054,
-    4182141402,
-    2262220987,
-    1957218950,
-    2094550054,
-    1846856260,
-    3499234137,
-    3086631065,
-    3054834317,
-    593829839,
-    522971108,
-    1162127370,
-    4233562270,
-    2780190687,
-    1558345254,
-    3716353056,
-    3518630848,
-    1158929937,
-    2038205856,
-    86116519,
-    4185661467,
-    975807626,
-    3910458990,
-    4124281183,
-    3361419439,
-    171334650,
-    2590402790,
-    2890570341,
-    2303184249,
-    385229009,
-    1998433745,
-    1717510093,
-    4022124023,
-    1429389803,
-    945128292,
-    904486530,
-    3869890846,
-    619875033,
-    459968607,
-    3743748793,
-    359054425,
-    1417363940,
-    3653985133,
-    255835594,
-    1047011733,
-    2763232252,
-    1329499601,
-    328661377,
-    2162274327,
-    2100532220,
-    4255182614,
-    4243119782,
-    3982047273,
-    4053789056,
-    401211099,
-    950731750,
-    1319785741,
-    32085358,
-    3882634684,
-    3117071189,
-    3554463148,
-    3570219049,
-    3535289452,
-    2314864456,
-    3913885196,
-    2763960513,
-    1079999262,
-    27130513,
-    3033873113,
-    2976581453,
-    2598189097,
-    595410904,
-    1572834111,
-    13319433,
-    1084574846,
-    2123388694,
-    560078433,
-    1679946323,
-    3518703473,
-    184634770,
-    296981500,
-    1646147798,
-    455591063,
-    1325348861,
-    3224952074,
-    1027242654,
-    2281956980,
-    4221373527,
-    1289566249,
-    4044928561,
-    882718761,
-    1510333659,
-    836581417,
-    1901166356,
-    2276405827,
-    4052965752,
-    1155765244,
-    503145996,
-    251209228,
-    495107308,
-    3944781937,
-    37459569,
-    4248015868,
-    4198082194,
-    1302400505,
-    4106658327,
-    680016782,
-    2319227476,
-    2738307068,
-    3929248764,
-    2850246066,
-    1824526196,
-    3912967080,
-    3044723416,
-    3133016299,
-    2517964682,
-    3647586740,
-    3653838348,
-    929101967,
-    3571454885,
-    2806296851,
-    977312655,
-    646282397,
-    3448018532,
-    824323032,
-    204234270,
-    1579585816,
-    3712763835,
-    1212872174,
-    3953984401,
-    3168953855,
-    2944827576,
-    1582841441,
-    2796901051,
-    3323682385,
-    1317058015,
-    2557550659,
-    1620634991,
-    2986830770,
-    2490492987,
-    1817271123,
-    40653745,
-    1696076631,
-    2466126792,
-    4169878842,
-    3251128023,
-    2444465148,
-    678695941,
-    2481746922,
-    2836440943,
-    774727851,
-    2246405597,
-    4028028350,
-    2524697596,
-    1977038330,
-    2817823941,
-    2219733501,
-    688216667,
-    3634598908,
-    3232633974,
-    2724625059,
-    3269075805,
-    3732640764,
-    2263349224,
-    1680746207,
-    2414984922,
-    2507457870,
-    50998433,
-    3092528578,
-    3712946115,
-    1543935193,
-    807276090,
-    1221183390,
-    172029722,
-    2122275289,
-    3990925720,
-    2261697609,
-    2736881867,
-    295017943,
-    3278176820,
-    3748965853,
-    3174324790,
-    1103903216,
-    3184177968,
-    1113409935,
-    2299842241,
-    2162986400,
-    1538342947,
-    4056442905,
-    1631434666,
-    205885885,
-    1594733696,
-    1955104493,
-    1022309772,
-    3820814597,
-    993150979,
-    1209418480,
-    1784441183,
-    3958731802,
-    2250225826,
-    3065160070,
-    2024071551,
-    107497541,
-    628544021,
-    2732195517,
-    4241486415,
-    3969279737,
-    870594305,
-    2916400082,
-    1193734351,
-    3202349435,
-    3831290364,
-    3282979782,
-    3928764629,
-    1308462133,
-    3216471040,
-    2433519008,
-    2022961611,
-    3604842236,
-    3374978006,
-    2855895374,
-    3496407048,
-    1482251215,
-    3994511488,
-    2997832431,
-    1132589448,
-    1348149915,
-    2092468906,
-    2451531615,
-    779021139,
-    3730093054,
-    3413713311,
-    1022915255,
-    2204920111,
-    2660843182,
-    1080545747,
-    1642805350,
-    1766422419,
-    4141567741,
-    1558990974,
-    4185590212,
-    2841468319,
-    701281393,
-    3325419312,
-    451957774,
-    357505993,
-    1156369516,
-    3187387500,
-    2259467579,
-    2678954464,
-    3154597438,
-    543558236,
-    2359973133,
-    1990431740,
-    2705477184,
-    1041368449,
-    3122368657,
-    3181646225,
-    1094423548,
-    2955375511,
-    2888125966,
-    153013225,
-    2936040203,
-    1758530522,
-    573901046,
-    3030911670,
-    1675922848,
-    4235213885,
-    4091916710,
-    2633682514,
-    4254584852,
-    2328748202,
-    3357301402,
-    3877813395,
-    2004567202,
-    2496297824,
-    3334207724,
-    1600149091,
-    293528591,
-    1782996825,
-    3757282300,
-    1107206446,
-    1092948665,
-    1797960910,
-    1206726575,
-    1496351055,
-    3021406120,
-    99347751,
-    3797204453,
-    1468919488,
-    797415788,
-    1314843976,
-    2934934694,
-    490769168,
-    1474506522,
-    3811268385,
-    864295921,
-    3081676220,
-    151810803,
-    2588618056,
-    2998120306,
-    416853049,
-    3495967422,
-    3233393284,
-    508007510,
-    759277550,
-    1971252067,
-    869050696,
-    810488476,
-    745556697,
-    789872778,
-    3362723943,
-    1617826947,
-    3260309823,
-    2197904616,
-    1199157863,
-    1643868273,
-    2430404313,
-    321630747,
-    2503194620,
-    3194725903,
-    2881225774,
-    3997952447,
-    1389644742,
-    2713718873,
-    3585511591,
-    1684282922,
-    3366848728,
-    284226441,
-    1541020250,
-    4018237905,
-    1369578001,
-    2424848261,
-    2654325647,
-    1626224034,
-    1081536219,
-    309040124,
-    123060826,
-    3997038726,
-    1670691893,
-    1543280290,
-    443347828,
-    1776629361,
-    3118548424,
-    478440524,
-    679771963,
-    3729929345,
-    4244789645,
-    2366506734,
-    2838165089,
-    1619778288,
-    1313182965,
-    3240680626,
-    1323407757,
-    883854656,
-    2194691858,
-    15502752,
-    3760372982,
-    1366337101,
-    3656163446,
-    295018543,
-    825595257,
-    57149555,
-    2563789125,
-    2353194283,
-    2636942752,
-    4026740269,
-    3570411982,
-    123108003,
-    3782362128,
-    1280126114,
-    1410849099,
-    4228502127,
-    3609540589,
-    3365041621,
-    269823086,
-    348988933,
-    1636389511,
-    2936586309,
-    2761603302,
-    2318200267,
-    449954059,
-    2895413148,
-    1755165354,
-    4274214049,
-    778500192,
-    3345707173,
-    3732136051,
-    721450866,
-    1600392975,
-    2466255445,
-    4050155669,
-    3541895912,
-    1139547465,
-    394654115,
-    1380991098,
-    3516240523,
-    2234361374,
-    1094817798,
-    744817486,
-    3564402361,
-    1452222566,
-    1851510470,
-    3619787319,
-    4265894873,
-    216945449,
-    3061690214,
-    2910557180,
-    255227811,
-    4167600590,
-    1587209598,
-    3157581152,
-    3184381405,
-    2572638469,
-    615748604,
-    2532518896,
-    1774874546,
-    599185303,
-    1561718045,
-    1742737136,
-    1674464100,
-    3136865519,
-    706016261,
-    2793529873,
-    3504981554,
-    4155122613,
-    2080953106,
-    1104362365,
-    2879917501,
-    850497536,
-    1392080469,
-    1287937401,
-    718877177,
-    1917966999,
-    1822823090,
-    3701632935,
-    3591222197,
-    2817335337,
-    1941148668,
-    3110479131,
-    3289213933,
-    583624926,
-    468372467,
-    1633850097,
-    2110223508,
-    898191441,
-    112745085,
-    4018820793,
-    3085119011,
-    2919626325,
-    3094857332,
-    2348201466,
-    2192810893,
-    4163160985,
-    1269075360,
-    3952316364,
-    2881886868,
-    439764402,
-    1584774136,
-    169674806,
-    3759072440,
-    102542696,
-    2996180816,
-    804899022,
-    1015552308,
-    963902061,
-    3504158761,
-    2002490364,
-    2806716850,
-    265778447,
-    4083122425,
-    181902171,
-    1238120570,
-    75986790,
-    1265796414,
-    899570100,
-    2988365258,
-    3655201337,
-    3654061472,
-    3061856840,
-    1077859090,
-    615341051,
-    3678875745,
-    3349230696,
-    3647606635,
-    2549309392,
-    1508570930,
-    1766401548,
-    1448448666,
-    1499923635,
-    2882994691,
-    3674863070,
-    3056042030,
-    4240893633,
-    1395113939,
-    2964622752,
-    1951208733,
-    3536941067,
-    4176581069,
-    1203545131,
-    3092754101,
-    246375791,
-    2736026107,
-    1069781886,
-    3687777340,
-    1564342316,
-    535067202,
-    1395923345,
-    3240977890,
-    1447712361,
-    2602027658,
-    718301639,
-    3123244280,
-    1032593647,
-    2840366496,
-    2680819379,
-    3839389658,
-    277023757,
-    1172110445,
-    1755648697,
-    2472176885,
-    223800276,
-    625975427,
-    976111724,
-    4145966869,
-    2789375411,
-    618087261,
-    249378857,
-    4058280485,
-    827698488,
-    1558001705,
-    3561482820,
-    2562485583,
-    4243138030,
-    615982737,
-    1220643281,
-    150685616,
-    3091876332,
-    1040775722,
-    669982125,
-    4116080964,
-    3582002820,
-    910398460,
-    1036475267,
-    3800912395,
-    146392076,
-    1686512349,
-    2326636627,
-    2839816704,
-    3502816184,
-    226836633,
-    3953733490,
-    257136089,
-    819503463,
-    2863084840,
-    1949759310,
-    210754155,
-    1367301635,
-    3822983876,
-    4273793488,
-    3635397748,
-    3930494584,
-    3127921440,
-    3167253437,
-    3868239231,
-    1859128680,
-    3480031018,
-    3810805277,
-    2677252364,
-    156014509,
-    3627739127,
-    2321729979,
-    1146476634,
-    4039938779,
-    1964254745,
-    2055836767,
-    119981689,
-    2629265310,
-    2448331885,
-    3737376990,
-    144116905,
-    2272221101,
-    2197874825,
-    1277245109,
-    2503770904,
-    360730278,
-    3489360962,
-    1166917451,
-    707478563,
-    4155586396,
-    162255877,
-    347505241,
-    4215670524,
-    3187066832,
-    2399809085,
-    2754074729,
-    4060703604,
-    628331516,
-    1304296041,
-    616435646,
-    4080527786,
-    1443829854,
-    2512398201,
-    708736129,
-    13107491,
-    3794803132,
-    2049792025,
-    2455417440,
-    3367313400,
-    3357250579,
-    3694383800,
-    2339901602,
-    3242843022,
-    2282454607,
-    1243764146,
-    835458563,
-    1297706389,
-    464259778,
-    1766994680,
-    1294403159,
-    2568098594,
-    3107165180,
-    4040340620,
-    3352361837,
-    1031290113,
-    2903897222,
-    1677700667,
-    3160388974,
-    107544081,
-    3044188332,
-    2285081596,
-    2835131395,
-    2984459037,
-    4174489262,
-    1236389532,
-    2938237924,
-    321459212,
-    3407526215,
-    300939750,
-    3441531391,
-    2909957084,
-    3192069648,
-    1849065716,
-    2524531022,
-    505940164,
-    4121643374,
-    3774892253,
-    3197739982,
-    2161102232,
-    2715370488,
-    1992893964,
-    1781864804,
-    587888644,
-    1039111164,
-    4237497041,
-    451382997,
-    969500141,
-    1415510495,
-    3743398113,
-    3027538652,
-    2525173102,
-    1708264968,
-    3366040354,
-    1100599986,
-    188347929,
-    2597020383,
-    2705434194,
-    2593884753,
-    3472123498,
-    2975894973,
-    3152745753,
-    1154919607,
-    1930923350,
-    3287039847,
-    1372881231,
-    2280400314,
-    3369343584,
-    2351620600,
-    2645135839,
-    2752766693,
-    1471851763,
-    1989520052,
-    1141965917,
-    1503477720,
-    653708953,
-    1765126703,
-    2432827426,
-    95470391,
-    2567901801,
-    2589449658,
-    4218799564,
-    3249265647,
-    3673811979,
-    210116709,
-    1593584949,
-    1791352211,
-    3457985288,
-    3345288309,
-    531559080,
-    2491124112,
-    3410158390,
-    4224872590,
-    3705139860,
-    162608772,
-    4258229445,
-    925559698,
-    3928842969,
-    4253051659,
-    3633746133,
-    3867307935,
-    3560665067,
-    798915737,
-    2945369269,
-    2677264274,
-    2278571792,
-    177111659,
-    85880059,
-    1297165140,
-    1630583316,
-    2232491275,
-    1848784182,
-    2487708241,
-    626480004,
-    3427283542,
-    2108571893,
-    304448521,
-    3332104493,
-    2244470522,
-    436416061,
-    221900294,
-    1502470404,
-    3552593177,
-    440421571,
-    450406196,
-    503094540,
-    3836822275,
-    2708915136,
-    3750617468,
-    1119744229,
-    3614752756,
-    921246433,
-    2285438321,
-    626892406,
-    2362972044,
-    72782198,
-    2929019254,
-    2795773560,
-    907126242,
-    155458798,
-    2798552666,
-    1404739463,
-    4285652249,
-    1998444837,
-    908777857,
-    872544165,
-    910429472,
-    135486769,
-    3457269042,
-    426360862,
-    1725011064,
-    296836635,
-    1322549027,
-    2044728014,
-    1530183840,
-    529742207,
-    4272200782,
-    1341516288,
-    2608484640,
-    41739659,
-    3260579369,
-    2745872368,
-    2894051250,
-    862784766,
-    3077271274,
-    3094180193,
-    3619626927,
-    3745223676,
-    2976066508,
-    2854085372,
-    2959147533,
-    3266548732,
-    1776526161,
-    3712296962,
-    1955871800,
-    2580096524,
-    2507709226,
-    3564865233,
-    948086521,
-    1548254487,
-    142465290,
-    1472185378,
-    1459457331,
-    2274226560,
-    3153451899,
-    492958971,
-    3563213618,
-    1285705317,
-    410274915,
-    3710645347,
-    1309728002,
-    2119793999,
-    1343794461,
-    4024173916,
-    2383939514,
-    955476870,
-    2698156268,
-    35240468,
-    2655147757,
-    3764205609,
-    3802564010,
-    170690025,
-    2311941439,
-    3181546731,
-    3866587616,
-    3648138580,
-    93914936,
-    170378107,
-    2120623674,
-    1064945649,
-    1618754372,
-    244668133,
-    247698428,
-    3669223677,
-    470277359,
-    1781765116,
-    1691572958,
-    1373856501,
-    2668769415,
-    1087394637,
-    1009983433,
-    2180701723,
-    4008405264,
-    2831059514,
-    2645120714,
-    2649103430,
-    2664825925,
-    790502615,
-    1739837626,
-    2293247016,
-    1784648440,
-    1887808856,
-    1788504755,
-    112452386,
-    1979978194,
-    3462674048,
-    2170273742,
-    538168945,
-    753954113,
-    374731234,
-    3715846592,
-    1962971231,
-    1860649552,
-    1378082995,
-    665789406,
-    1717555224,
-    139011596,
-    1375043498,
-    1618544981,
-    1889460471,
-    2262321736,
-    1788301425,
-    1652168174,
-    2668680621,
-    2636946065,
-    2856623532,
-    2759951687,
-    959681532,
-    3209399506,
-    3055195668,
-    1227221002,
-    508217552,
-    3289969989,
-    243178923,
-    2956189845,
-    3075866530,
-    2274779301,
-    3940720663,
-    3998230222,
-    1178317551,
-    4016096296,
-    1545450160,
-    2842919847,
-    314809953,
-    2952850186,
-    3747079365,
-    4147239510,
-    169135842,
-    1332643570,
-    2994529201,
-    973521782,
-    1584369690,
-    1043738701,
-    2851900832,
-    290391815,
-    283209196,
-    2468230023,
-    1164221089,
-    1991787192,
-    3358097187,
-    51041423,
-    52882140,
-    2339018837,
-    2053214130,
-    3757479030,
-    158160339,
-    853200279,
-    1986584654,
-    438318340,
-    827246872,
-    3299488628,
-    2924263085,
-    3472029049,
-    2736844435,
-    677668732,
-    604894932,
-    1158021131,
-    1400019344,
-    2268204687,
-    1450415100,
-    3854557817,
-    1543646433,
-    1278448636,
-    342615870,
-    1554194368,
-    3080024605,
-    3423702268,
-    1675764636,
-    1622381564,
-    2078849875,
-    2113115132,
-    1380160211,
-    3132876285,
-    125015036,
-    269576093,
-    94145952,
-    2777172031,
-    2683080096,
-    3812456892,
-    488500848,
-    3270430997,
-    2895151306,
-    116376005,
-    400248103,
-    406044930,
-    1616846013,
-    10142671,
-    763027711,
-    225200779,
-    1062250709,
-    2013867381,
-    2113506324,
-    1692932387,
-    1827244161,
-    3124618210,
-    2096472894,
-    2924146124,
-    2128251367,
-    2433358586,
-    1939359710,
-    2593325766,
-    2879917723,
-    694743357,
-    2902069960,
-    220008971,
-    3090408469,
-    917019124,
-    1705716306,
-    3263901372,
-    3347863687,
-    3447882276,
-    1661163736,
-    3617689692,
-    3928555688,
-    1057578789,
-    435256475,
-    4101009465,
-    1941403425,
-    198967948,
-    3733675151,
-    2043684541,
-    3517169445,
-    2226776400,
-    2853403709,
-    529383565,
-    2807448986,
-    4234287173,
-    1019457583,
-    1022544883,
-    2493146691,
-    1054461787,
-    1008886329,
-    1136775085,
-    1191015885,
-    1196280518,
-    1979847999,
-    50385656,
-    1918742169,
-    3999472204,
-    3697687030,
-    2220475432,
-    2358141757,
-    2360004627,
-    4245257809,
-    236660303,
-    429277936,
-    342159236,
-    2622612602,
-    371428004,
-    373079619,
-    643418617,
-    2095027856,
-    1071164424,
-    1136911283,
-    1548491889,
-    2169307971,
-    375530199,
-    1510422521,
-    3151638847,
-    1698730948,
-    2231688008,
-    2604576561,
-    2771938750,
-    2996594997,
-    289648234,
-    348584153,
-    2748350697,
-    2926633629,
-    2123683379,
-    369686787,
-    742917749,
-    3538158875,
-    2937761472,
-    1545298048,
-    1321616112,
-    2855506940,
-    900522183,
-    1578775276,
-    2217833278,
-    2012838864,
-    3753486980,
-    2839765116,
-    2464905186,
-    2621255555,
-    1305703280,
-    861753115,
-    3319278167,
-    3063300848,
-    149720480,
-    1082941229,
-    3337532056,
-    2248357849,
-    3675926744,
-    1508550646,
-    2289803479,
-    3456899824,
-    3931641900,
-    3970432934,
-    3419674548,
-    1093210099,
-    456043370,
-    848380423,
-    1287304304,
-    1526654696,
-    2055664760,
-    1373166395,
-    4291477370,
-    2195550588,
-    2847102741,
-    3399062057,
-    1641565587,
-    2888753905,
-    3579593979,
-    3653059026,
-    3757851979,
-    2922615804,
-    2919796598,
-    1553476262,
-    2566666743,
-    3759503594,
-    550831114,
-    3761155209,
-    3762806824,
-    3902853271,
-    4140081844,
-    14244860,
-    3847846774,
-    150820676,
-    1278818058,
-    850592577,
-    1206571206,
-    1734446471,
-    2117320444,
-    1382106590,
-    2436009347,
-    2118972059,
-    2951272396,
-    36096192,
-    117998987,
-    473485679,
-    2244928358,
-    476788909,
-    3489269251,
-    610429940,
-    480092139,
-    481743754,
-    871966503,
-    918189168,
-    601656217,
-    933769938,
-    939671928,
-    1799299383,
-    3312467582,
-    1149665466,
-    3006548167,
-    1310740861,
-    3602693817,
-    1461645203,
-    3367691969,
-    1800404122,
-    3486057732,
-    1862284649,
-    2076833303,
-    2213411495,
-    2805256437,
-    3927915220,
-    3000904950,
-    2094647776,
-    3333131702,
-    1315613425,
-    3752211294,
-    603915804,
-    3505028338,
-    663258455,
-    3322500634,
-    1612225949,
-    3606320646,
-    157110413,
-    1352397672,
-    3861006967,
-    452208841,
-    18776483,
-    1058429216,
-    37009196,
-    564884461,
-    876864198,
-    2952260510,
-    2860348412,
-    928261291,
-    1164724902,
-    2775815164,
-    1332774287,
-    780957373,
-    939415664,
-    1513770932,
-    788046331,
-    1692600167,
-    4069810315,
-    673708384,
-    4024252457,
-    1932614728,
-    2148510256,
-    3131224670,
-    2388524817,
-    2460489993,
-    2676385521,
-    826214242,
-    3692647551,
-    3063508455,
-    3071766530,
-    2063832060,
-    1525861001,
-    3073418145,
-    837715723,
-    3075069760,
-    3076721375,
-    3078372990,
-    983243705,
-    3083327835,
-    171307615,
-    1824016656,
-    3084979450,
-    1310404265,
-    1775308984,
-    3114708520,
-    3116360135,
-    3121314980,
-    3134527900,
-    1691646294,
-    2804281092,
-    97231530,
-    3136179515,
-    3204260786,
-    3276225962,
-    1220749418,
-    3588205699,
-    3874089391,
-    4044115788,
-    3268751013,
-    743407979,
-    166253838,
-    1356063462,
-    1368383673,
-    2279700640,
-    2130747644,
-    3945795573,
-    2780898906,
-    3635542517,
-    425022309,
-    517919178,
-    4061558677,
-    2190437442,
-    543621065,
-    753756604,
-    2500819054,
-    1004589179,
-    1165671422,
-    30433743,
-    3444275347,
-    1335363438,
-    1913735398,
-    1265998516,
-    3829325073,
-    3662767579,
-    463084678,
-    1351676723,
-    1391866096,
-    3398925952,
-    1631216488,
-    815757910,
-    1915438939,
-    2427834344,
-    1445161581,
-    1890300748,
-    2864863800,
-    1961990747,
-    575205902,
-    2037710159,
-    2037814253,
-    617312262,
-    3732916270,
-    783918780,
-    2257843797,
-    2096388952,
-    2338272340,
-    1434223270,
-    578132535,
-    1980341560,
-    1002144380,
-    3244716568,
-    4258414038,
-    3271748023,
-    3304438238,
-    3717523241,
-    3370185097,
-    3435931956,
-    1957265068,
-    3602522282,
-    2547657777,
-    439998433,
-    3838648480,
-    3913593633,
-    3989799199,
-    906176560,
-    1894133125,
-    4046301857,
-    4242327928,
-    630592085,
-    2693892518,
-    4292991777,
-    545678922,
-    125792961,
-    3015046341,
-    132755933,
-    2615111110,
-    1570165302,
-    1440646342,
-    436066778,
-    565233904,
-    600906020,
-    602222721,
-    3951925872,
-    1496901698,
-    1522901980,
-    2785441472,
-    3041450802,
-    1637661947,
-    2127660080,
-    3487022798,
-    2269114589,
-    1314834580,
-    2315690100,
-    3817149113,
-    4091670162,
-    1431749301,
-    1858116930,
-    2213946343,
-    2225172640,
-    2263866576,
-    2727022058,
-    2752967311,
-    2864705739,
-    3052439312,
-    3510257966,
-    2614053317,
-    3297860332,
-    3670298840,
-    3732709413,
-    3788324110,
-    4098876453,
-    4290374884,
-    1623013158,
-    3381478137,
-    17185761,
-    3931288033,
-    2890638791,
-    330388453,
-    346929928,
-    2022347217,
-    4083347580,
-    533021259,
-    564302770,
-    1917602962,
-    680157484,
-    3264086791,
-    3727034815,
-    798549062,
-    3068463300,
-    669812542,
-    1965902997,
-    2311072371,
-    3079287749,
-    2542834724,
-    1587730355,
-    2558655180,
-    1838763297,
-    4172568578,
-    2160380860,
-    2950446516,
-    1830851200,
-    3214537066,
-    3234673086,
-    3652695478,
-    3103302036,
-    3465954368,
-    4180570743,
-    3534518722,
-    371186900,
-    4091394002,
-    1013756921,
-    443558693,
-    591140762,
-    656610661,
-    2064733527,
-    3808408202,
-    983299427,
-    4217306348,
-    1164218401,
-    2036361232,
-    3237903670,
-    2970183398,
-    2293637521,
-    135920445,
-    1596005536,
-    868652905,
-    1191735827,
-    3987079331,
-    1365842164,
-    1508074873,
-    1642818143,
-    3436143898,
-    4105051793,
-    1863199739,
-    3425841570,
-    1070791291,
-    2135340676,
-    2639720559,
-    3364388739,
-    3797761273,
-    2092100514,
-    2098706974,
-    2329992200,
-    414444763,
-    2759250216,
-    2913136690,
-    3012980338,
-    3327770644,
-    4128942283,
-    3362344229,
-    161668409,
-    3401762422,
-    2852854788,
-    4237092412,
-    1245448751,
-    3702405475,
-    918849409,
-    3829682756,
-    1612361408,
-    255302575,
-    414620710,
-    386293029,
-    618761615,
-    686024761,
-    744062262,
-    1502028603,
-    1543798545,
-    1641415225,
-    1548121999,
-    2257971049,
-    2124837447,
-    878733439,
-    2340670452,
-    2674090849,
-    3118011750,
-    2816338013,
-    178571546,
-    2841008029,
-    3249261197,
-    370232173,
-    4092487128,
-    3787567939,
-    3898287302,
-    4142016703,
-    4285779501,
-    30663912,
-    151672195,
-    180913835,
-    3534235309,
-    34183582,
-    4083161638,
-    651464351,
-    1410311776,
-    371621315,
-    421602934,
-    458937500,
-    2710583246,
-    712168842,
-    730943059,
-    1519723107,
-    875212982,
-    1247793383,
-    4217322139,
-    989813600,
-    1057606514,
-    3764662384,
-    1443547269,
-    3066811685,
-    3598957382,
-    1791427568,
-    1171541710,
-    3930727258,
-    1473799048,
-    1296054774,
-    1747355813,
-    765238787,
-    2023008475,
-    1190147516,
-    2344328209,
-    2495155989,
-    2577859137,
-    2857814560,
-    3127329373,
-    3296722158,
-    2773229577,
-    3376009661,
-    3450001968,
-    920941800,
-    3526837441,
-    3858973601,
-    1702168830,
-    4088613871,
-    1464587427,
-    223310468,
-    388034151,
-    2346547796,
-    1663234329,
-    1750829822,
-    1967643923,
-    2881302403,
-    2278706468,
-    2326990117,
-    2511346984,
-    3088785099,
-    2616085763,
-    3027500544,
-    3417583519,
-    4178218543,
-    1412908157,
-    797934924,
-    3533637837,
-    1449907751,
-    3362830643,
-    1451831482,
-    2637935122,
-    3070114915,
-    3023287679,
-    551924251,
-    1669930486,
-    46736908,
-    2870852215,
-    1120149824,
-    2923708820,
-    3887377256,
-    3464197236,
-    4241374559,
-    527665290,
-    996663016,
-    885020215,
-    1763758554,
-    3059119137,
-    2555315060,
-    2762094724,
-    2530899578,
-    2770161927,
-    2262137600,
-    3547456240,
-    858902117,
-    1140367371,
-    1215030156,
-    443490822,
-    294390719,
-    3032677281,
-    1917451875,
-    4184019303,
-    3277199633,
-    1271484400,
-    1297294717,
-    3560552546,
-    171494987,
-    195244192,
-    3002890475,
-    1811839150,
-    265392489,
-    1461398554,
-    3205759417,
-    333855951,
-    529068443,
-    660038281,
-    557400685,
-    663341511,
-    930804377,
-    1922045399,
-    716890919,
-    162167595,
-    1654776395,
-    1779143013,
-    1123617794,
-    2984325996,
-    1162789888,
-    1318479490,
-    1235468610,
-    3561562003,
-    1486207619,
-    1551372768,
-    1850331254,
-    3255947500,
-    1037370721,
-    1989327599,
-    2137526937,
-    835638766,
-    2269130237,
-    1962162282,
-    3244209297,
-    2330636993,
-    3095831808,
-    1396344138,
-    2603020391,
-    3434076295,
-    3280064277,
-    2656211099,
-    3335250889,
-    2550961007,
-    3510242586,
-    3536471583,
-    3950980241,
-    4033586023,
-    117250846,
-    3088282680,
-    4041974454,
-    4244540017,
-    1167160774,
-    899320334,
-    1200870684,
-    1752686878,
-    1906988301,
-    3804101227,
-    2575525651,
-    2919787747,
-    3508792859,
-    3548535223,
-    3783756895,
-    3797961332,
-    4043078107,
-    3115038057,
-    2313593054,
-    49456560,
-    592180731,
-    1051471757,
-    1097775533,
-    706238670,
-    877895868,
-    1173092699,
-    1461897718,
-    1767704813,
-    1770165905,
-    1923453688,
-    2212501241,
-    2305269460,
-    2488410748,
-    3782099915,
-    2844616706,
-    3383007207,
-    3392887901,
-    504514034,
-    3765247327,
-    1000070091,
-    3727494858,
-    3657635382,
-    3839047923,
-    3886529747,
-    4069720347,
-    4164704452,
-    342197850,
-    3540244297,
-    2513230733,
-    4117704995,
-    3367298820,
-    2680283743,
-    3119663365,
-    3697738938,
-    545363837,
-    163402553,
-    5908395,
-    129135650,
-    2289183712,
-    200922300,
-    761731755,
-    894529125,
-    1086964761,
-    1168927492,
-    2100052708,
-    2438466459,
-    3390051757,
-    2498042266,
-    2557754096,
-    2600961503,
-    487719832,
-    703543228,
-    2726532092,
-    4199470013,
-    3142155593,
-    2550501832,
-    4076840151,
-    200553094,
-    380957745,
-    572905105,
-    462664429,
-    1466804584,
-    330249537,
-    2605012269,
-    491456522,
-    4126287524,
-    502863753,
-    952536201,
-    3510682541,
-    1137442027,
-    1665981878,
-    1761469971,
-    3085467405,
-    2045285083,
-    796985462,
-    3433956341,
-    2217966239,
-    2183547611,
-    2279273489,
-    1916983087,
-    2348676810,
-    2403632109,
-    2409539315,
-    545986953,
-    176166202,
-    2477389837,
-    2573160348,
-    2796513469,
-    3972309363,
-    528662843,
-    1038982109,
-    1125913837,
-    1318081294,
-    1417425499,
-  };
-  return descriptors_with_coding_scheme;
-}
diff --git a/tools/dis/dis.cpp b/tools/dis/dis.cpp
index 6a2e269..2a6f411 100644
--- a/tools/dis/dis.cpp
+++ b/tools/dis/dis.cpp
@@ -60,7 +60,7 @@
       argv0, argv0);
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
new file mode 100644
index 0000000..481942f
--- /dev/null
+++ b/tools/fuzz/fuzz.cpp
@@ -0,0 +1,480 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cassert>
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <functional>
+#include <sstream>
+#include <string>
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/replayer.h"
+#include "source/fuzz/shrinker.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/log.h"
+#include "source/spirv_fuzzer_options.h"
+#include "source/util/string_utils.h"
+#include "tools/io.h"
+#include "tools/util/cli_consumer.h"
+
+namespace {
+
+// Check that the std::system function can actually be used.
+bool CheckExecuteCommand() {
+  int res = std::system(nullptr);
+  return res != 0;
+}
+
+// Execute a command using the shell.
+// Returns true if and only if the command's exit status was 0.
+bool ExecuteCommand(const std::string& command) {
+  errno = 0;
+  int status = std::system(command.c_str());
+  assert(errno == 0 && "failed to execute command");
+  // The result returned by 'system' is implementation-defined, but is
+  // usually the case that the returned value is 0 when the command's exit
+  // code was 0.  We are assuming that here, and that's all we depend on.
+  return status == 0;
+}
+
+// Status and actions to perform after parsing command-line arguments.
+enum class FuzzActions {
+  FUZZ,    // Run the fuzzer to apply transformations in a randomized fashion.
+  REPLAY,  // Replay an existing sequence of transformations.
+  SHRINK,  // Shrink an existing sequence of transformations with respect to an
+           // interestingness function.
+  STOP     // Do nothing.
+};
+
+struct FuzzStatus {
+  FuzzActions action;
+  int code;
+};
+
+void PrintUsage(const char* program) {
+  // NOTE: Please maintain flags in lexicographical order.
+  printf(
+      R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
+
+USAGE: %s [options] <input.spv> -o <output.spv>
+
+The SPIR-V binary is read from <input.spv>, which must have extension .spv.  If
+<input.json> is also present, facts about the SPIR-V binary are read from this
+file.
+
+The transformed SPIR-V binary is written to <output.spv>.  Human-readable and
+binary representations of the transformations that were applied to obtain this
+binary are written to <output.json> and <output.transformations>, respectively.
+
+NOTE: The fuzzer is a work in progress.
+
+Options (in lexicographical order):
+
+  -h, --help
+               Print this help.
+  --replay
+               File from which to read a sequence of transformations to replay
+               (instead of fuzzing)
+  --seed
+               Unsigned 32-bit integer seed to control random number
+               generation.
+  --shrink
+               File from which to read a sequence of transformations to shrink
+               (instead of fuzzing)
+  --shrinker-step-limit
+               Unsigned 32-bit integer specifying maximum number of steps the
+               shrinker will take before giving up.  Ignored unless --shrink
+               is used.
+  --interestingness
+               Path to an interestingness function to guide shrinking: a script
+               that returns 0 if and only if a given binary is interesting.
+               Required if --shrink is provided; disallowed otherwise.
+  --version
+               Display fuzzer version information.
+
+)",
+      program, program);
+}
+
+// Message consumer for this tool.  Used to emit diagnostics during
+// initialization and setup. Note that |source| and |position| are irrelevant
+// here because we are still not processing a SPIR-V input file.
+void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
+                    const spv_position_t& /*position*/, const char* message) {
+  if (level == SPV_MSG_ERROR) {
+    fprintf(stderr, "error: ");
+  }
+  fprintf(stderr, "%s\n", message);
+}
+
+bool EndsWithSpv(const std::string& filename) {
+  std::string dot_spv = ".spv";
+  return filename.length() >= dot_spv.length() &&
+         0 == filename.compare(filename.length() - dot_spv.length(),
+                               filename.length(), dot_spv);
+}
+
+FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
+                      std::string* out_binary_file,
+                      std::string* replay_transformations_file,
+                      std::string* interestingness_function_file,
+                      std::string* shrink_transformations_file,
+                      spvtools::FuzzerOptions* fuzzer_options) {
+  uint32_t positional_arg_index = 0;
+
+  for (int argi = 1; argi < argc; ++argi) {
+    const char* cur_arg = argv[argi];
+    if ('-' == cur_arg[0]) {
+      if (0 == strcmp(cur_arg, "--version")) {
+        spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
+                       spvSoftwareVersionDetailsString());
+        return {FuzzActions::STOP, 0};
+      } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
+        PrintUsage(argv[0]);
+        return {FuzzActions::STOP, 0};
+      } else if (0 == strcmp(cur_arg, "-o")) {
+        if (out_binary_file->empty() && argi + 1 < argc) {
+          *out_binary_file = std::string(argv[++argi]);
+        } else {
+          PrintUsage(argv[0]);
+          return {FuzzActions::STOP, 1};
+        }
+      } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        *replay_transformations_file = std::string(split_flag.second);
+      } else if (0 == strncmp(cur_arg, "--interestingness=",
+                              sizeof("--interestingness=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        *interestingness_function_file = std::string(split_flag.second);
+      } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        *shrink_transformations_file = std::string(split_flag.second);
+      } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        char* end = nullptr;
+        errno = 0;
+        const auto seed =
+            static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
+        assert(end != split_flag.second.c_str() && errno == 0);
+        fuzzer_options->set_random_seed(seed);
+      } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
+                              sizeof("--shrinker-step-limit=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        char* end = nullptr;
+        errno = 0;
+        const auto step_limit =
+            static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
+        assert(end != split_flag.second.c_str() && errno == 0);
+        fuzzer_options->set_shrinker_step_limit(step_limit);
+      } else if ('\0' == cur_arg[1]) {
+        // We do not support fuzzing from standard input.  We could support
+        // this if there was a compelling use case.
+        PrintUsage(argv[0]);
+        return {FuzzActions::STOP, 0};
+      }
+    } else if (positional_arg_index == 0) {
+      // Binary input file name
+      assert(in_binary_file->empty());
+      *in_binary_file = std::string(cur_arg);
+      positional_arg_index++;
+    } else {
+      spvtools::Error(FuzzDiagnostic, nullptr, {},
+                      "Too many positional arguments specified");
+      return {FuzzActions::STOP, 1};
+    }
+  }
+
+  if (in_binary_file->empty()) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
+    return {FuzzActions::STOP, 1};
+  }
+
+  if (!EndsWithSpv(*in_binary_file)) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    "Input filename must have extension .spv");
+    return {FuzzActions::STOP, 1};
+  }
+
+  if (out_binary_file->empty()) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
+    return {FuzzActions::STOP, 1};
+  }
+
+  if (!EndsWithSpv(*out_binary_file)) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    "Output filename must have extension .spv");
+    return {FuzzActions::STOP, 1};
+  }
+
+  if (!replay_transformations_file->empty()) {
+    // A replay transformations file was given, thus the tool is being invoked
+    // in replay mode.
+    if (!shrink_transformations_file->empty()) {
+      spvtools::Error(
+          FuzzDiagnostic, nullptr, {},
+          "The --replay and --shrink arguments are mutually exclusive.");
+      return {FuzzActions::STOP, 1};
+    }
+    if (!interestingness_function_file->empty()) {
+      spvtools::Error(FuzzDiagnostic, nullptr, {},
+                      "The --replay and --interestingness arguments are "
+                      "mutually exclusive.");
+      return {FuzzActions::STOP, 1};
+    }
+    return {FuzzActions::REPLAY, 0};
+  }
+
+  if (!shrink_transformations_file->empty() ^
+      !interestingness_function_file->empty()) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    "Both or neither of the --shrink and --interestingness "
+                    "arguments must be provided.");
+    return {FuzzActions::STOP, 1};
+  }
+
+  if (!shrink_transformations_file->empty()) {
+    // The tool is being invoked in shrink mode.
+    assert(!interestingness_function_file->empty() &&
+           "An error should have been raised if --shrink but not --interesting "
+           "was provided.");
+    return {FuzzActions::SHRINK, 0};
+  }
+
+  return {FuzzActions::FUZZ, 0};
+}
+
+bool ParseTransformations(
+    const std::string& transformations_file,
+    spvtools::fuzz::protobufs::TransformationSequence* transformations) {
+  std::ifstream transformations_stream;
+  transformations_stream.open(transformations_file,
+                              std::ios::in | std::ios::binary);
+  auto parse_success =
+      transformations->ParseFromIstream(&transformations_stream);
+  transformations_stream.close();
+  if (!parse_success) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    ("Error reading transformations from file '" +
+                     transformations_file + "'")
+                        .c_str());
+    return false;
+  }
+  return true;
+}
+
+bool Replay(const spv_target_env& target_env,
+            const std::vector<uint32_t>& binary_in,
+            const spvtools::fuzz::protobufs::FactSequence& initial_facts,
+            const std::string& replay_transformations_file,
+            std::vector<uint32_t>* binary_out,
+            spvtools::fuzz::protobufs::TransformationSequence*
+                transformations_applied) {
+  spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
+  if (!ParseTransformations(replay_transformations_file,
+                            &transformation_sequence)) {
+    return false;
+  }
+  spvtools::fuzz::Replayer replayer(target_env);
+  replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
+  auto replay_result_status =
+      replayer.Run(binary_in, initial_facts, transformation_sequence,
+                   binary_out, transformations_applied);
+  return !(replay_result_status !=
+           spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
+}
+
+bool Shrink(const spv_target_env& target_env,
+            spv_const_fuzzer_options fuzzer_options,
+            const std::vector<uint32_t>& binary_in,
+            const spvtools::fuzz::protobufs::FactSequence& initial_facts,
+            const std::string& shrink_transformations_file,
+            const std::string& interestingness_function_file,
+            std::vector<uint32_t>* binary_out,
+            spvtools::fuzz::protobufs::TransformationSequence*
+                transformations_applied) {
+  spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
+  if (!ParseTransformations(shrink_transformations_file,
+                            &transformation_sequence)) {
+    return false;
+  }
+  spvtools::fuzz::Shrinker shrinker(target_env,
+                                    fuzzer_options->shrinker_step_limit);
+  shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
+
+  spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
+      [interestingness_function_file](std::vector<uint32_t> binary,
+                                      uint32_t reductions_applied) -> bool {
+    std::stringstream ss;
+    ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
+       << ".spv";
+    const auto spv_file = ss.str();
+    const std::string command =
+        std::string(interestingness_function_file) + " " + spv_file;
+    auto write_file_succeeded =
+        WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
+    (void)(write_file_succeeded);
+    assert(write_file_succeeded);
+    return ExecuteCommand(command);
+  };
+
+  auto shrink_result_status = shrinker.Run(
+      binary_in, initial_facts, transformation_sequence,
+      interestingness_function, binary_out, transformations_applied);
+  return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
+             shrink_result_status ||
+         spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
+             shrink_result_status;
+}
+
+bool Fuzz(const spv_target_env& target_env,
+          const spvtools::FuzzerOptions& fuzzer_options,
+          const std::vector<uint32_t>& binary_in,
+          const spvtools::fuzz::protobufs::FactSequence& initial_facts,
+          std::vector<uint32_t>* binary_out,
+          spvtools::fuzz::protobufs::TransformationSequence*
+              transformations_applied) {
+  spvtools::fuzz::Fuzzer fuzzer(target_env);
+  fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
+  auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options,
+                                       binary_out, transformations_applied);
+  if (fuzz_result_status !=
+      spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+
+int main(int argc, const char** argv) {
+  std::string in_binary_file;
+  std::string out_binary_file;
+  std::string replay_transformations_file;
+  std::string interestingness_function_file;
+  std::string shrink_transformations_file;
+
+  spvtools::FuzzerOptions fuzzer_options;
+
+  FuzzStatus status =
+      ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
+                 &replay_transformations_file, &interestingness_function_file,
+                 &shrink_transformations_file, &fuzzer_options);
+
+  if (status.action == FuzzActions::STOP) {
+    return status.code;
+  }
+
+  std::vector<uint32_t> binary_in;
+  if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
+    return 1;
+  }
+
+  spvtools::fuzz::protobufs::FactSequence initial_facts;
+  const std::string dot_spv(".spv");
+  std::string in_facts_file =
+      in_binary_file.substr(0, in_binary_file.length() - dot_spv.length()) +
+      ".facts";
+  std::ifstream facts_input(in_facts_file);
+  if (facts_input) {
+    std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
+                                  std::istreambuf_iterator<char>());
+    facts_input.close();
+    if (google::protobuf::util::Status::OK !=
+        google::protobuf::util::JsonStringToMessage(facts_json_string,
+                                                    &initial_facts)) {
+      spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
+      return 1;
+    }
+  }
+
+  std::vector<uint32_t> binary_out;
+  spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
+
+  spv_target_env target_env = kDefaultEnvironment;
+
+  switch (status.action) {
+    case FuzzActions::FUZZ:
+      if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
+                &binary_out, &transformations_applied)) {
+        return 1;
+      }
+      break;
+    case FuzzActions::REPLAY:
+      if (!Replay(target_env, binary_in, initial_facts,
+                  replay_transformations_file, &binary_out,
+                  &transformations_applied)) {
+        return 1;
+      }
+      break;
+    case FuzzActions::SHRINK: {
+      if (!CheckExecuteCommand()) {
+        std::cerr << "could not find shell interpreter for executing a command"
+                  << std::endl;
+        return 1;
+      }
+      if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
+                  shrink_transformations_file, interestingness_function_file,
+                  &binary_out, &transformations_applied)) {
+        return 1;
+      }
+    } break;
+    default:
+      assert(false && "Unknown fuzzer action.");
+      break;
+  }
+
+  if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
+                           binary_out.size())) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
+    return 1;
+  }
+
+  std::string output_file_prefix =
+      out_binary_file.substr(0, out_binary_file.length() - dot_spv.length());
+  std::ofstream transformations_file;
+  transformations_file.open(output_file_prefix + ".transformations",
+                            std::ios::out | std::ios::binary);
+  bool success =
+      transformations_applied.SerializeToOstream(&transformations_file);
+  transformations_file.close();
+  if (!success) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    "Error writing out transformations binary");
+    return 1;
+  }
+
+  std::string json_string;
+  auto json_options = google::protobuf::util::JsonOptions();
+  json_options.add_whitespace = true;
+  auto json_generation_status = google::protobuf::util::MessageToJsonString(
+      transformations_applied, &json_string, json_options);
+  if (json_generation_status != google::protobuf::util::Status::OK) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {},
+                    "Error writing out transformations in JSON format");
+    return 1;
+  }
+
+  std::ofstream transformations_json_file(output_file_prefix + ".json");
+  transformations_json_file << json_string;
+  transformations_json_file.close();
+
+  return 0;
+}
diff --git a/tools/io.h b/tools/io.h
index aaf8fcd..97a3163 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -37,6 +37,7 @@
     if (ftell(fp) == -1L) {
       if (ferror(fp)) {
         fprintf(stderr, "error: error reading file '%s'\n", filename);
+        if (use_file) fclose(fp);
         return false;
       }
     } else {
@@ -45,6 +46,7 @@
             stderr,
             "error: file size should be a multiple of %zd; file '%s' corrupt\n",
             sizeof(T), filename);
+        if (use_file) fclose(fp);
         return false;
       }
     }
@@ -69,6 +71,7 @@
     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);
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
index fb44a37..82d430e 100644
--- a/tools/link/linker.cpp
+++ b/tools/link/linker.cpp
@@ -23,6 +23,7 @@
 #include "tools/io.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(27, 95);
   printf(
       R"(%s - Link SPIR-V binary files together.
 
@@ -39,10 +40,10 @@
   --allow-partial-linkage Allow partial linkage by accepting imported symbols to be unresolved.
   --verify-ids            Verify that IDs in the resulting modules are truly unique.
   --version               Display linker version information
-  --target-env            {vulkan1.0|spv1.0|spv1.1|spv1.2|opencl2.1|opencl2.2}
-                          Use Vulkan1.0/SPIR-V1.0/SPIR-V1.1/SPIR-V1.2/OpenCL-2.1/OpenCL2.2 validation rules.
+  --target-env            {%s}
+                          Use validation rules from the specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
 int main(int argc, char** argv) {
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index fb79427..cce1053 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -59,7 +59,7 @@
   return ss.str();
 }
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 std::string GetLegalizationPasses() {
   spvtools::Optimizer optimizer(kDefaultEnvironment);
@@ -79,13 +79,20 @@
   return GetListOfPassesAsString(optimizer);
 }
 
-std::string GetWebGPUPasses() {
+std::string GetVulkanToWebGPUPasses() {
   spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0);
-  optimizer.RegisterWebGPUPasses();
+  optimizer.RegisterVulkanToWebGPUPasses();
+  return GetListOfPassesAsString(optimizer);
+}
+
+std::string GetWebGPUToVulkanPasses() {
+  spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1);
+  optimizer.RegisterWebGPUToVulkanPasses();
   return GetListOfPassesAsString(optimizer);
 }
 
 void PrintUsage(const char* program) {
+  std::string target_env_list = spvTargetEnvList(16, 80);
   // NOTE: Please maintain flags in lexicographical order.
   printf(
       R"(%s - Optimize a SPIR-V binary file.
@@ -99,91 +106,118 @@
 
 NOTE: The optimizer is a work in progress.
 
-Options (in lexicographical order):
+Options (in lexicographical order):)",
+      program, program);
+  printf(R"(
   --ccp
                Apply the conditional constant propagation transform.  This will
                propagate constant values throughout the program, and simplify
                expressions and conditional jumps with known predicate
                values.  Performed on entry point call tree functions and
-               exported functions.
+               exported functions.)");
+  printf(R"(
   --cfg-cleanup
                Cleanup the control flow graph. This will remove any unnecessary
                code from the CFG like unreachable code. Performed on entry
-               point call tree functions and exported functions.
+               point call tree functions and exported functions.)");
+  printf(R"(
   --combine-access-chains
                Combines chained access chains to produce a single instruction
-               where possible.
+               where possible.)");
+  printf(R"(
   --compact-ids
                Remap result ids to a compact range starting from %%1 and without
-               any gaps.
+               any gaps.)");
+  printf(R"(
   --convert-local-access-chains
                Convert constant index access chain loads/stores into
                equivalent load/stores with inserts and extracts. Performed
                on function scope variables referenced only with load, store,
                and constant index access chains in entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --copy-propagate-arrays
                Does propagation of memory references when an array is a copy of
                another.  It will only propagate an array if the source is never
-               written to, and the only store to the target is the copy.
-  --eliminate-common-uniform
-               Perform load/load elimination for duplicate uniform values.
-               Converts any constant index access chain uniform loads into
-               its equivalent load and extract. Some loads will be moved
-               to facilitate sharing. Performed only on entry point
-               call tree functions.
+               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"(
   --eliminate-dead-branches
                Convert conditional branches with constant condition to the
                indicated unconditional brranch. Delete all resulting dead
-               code. Performed only on entry point call tree functions.
+               code. Performed only on entry point call tree functions.)");
+  printf(R"(
   --eliminate-dead-code-aggressive
                Delete instructions which do not contribute to a function's
-               output. Performed only on entry point call tree functions.
+               output. Performed only on entry point call tree functions.)");
+  printf(R"(
   --eliminate-dead-const
-               Eliminate dead constants.
+               Eliminate dead constants.)");
+  printf(R"(
   --eliminate-dead-functions
                Deletes functions that cannot be reached from entry points or
-               exported functions.
+               exported functions.)");
+  printf(R"(
   --eliminate-dead-inserts
                Deletes unreferenced inserts into composites, most notably
                unused stores to vector components, that are not removed by
-               aggressive dead code elimination.
+               aggressive dead code elimination.)");
+  printf(R"(
   --eliminate-dead-variables
-               Deletes module scope variables that are not referenced.
+               Deletes module scope variables that are not referenced.)");
+  printf(R"(
   --eliminate-insert-extract
                DEPRECATED.  This pass has been replaced by the simplification
                pass, and that pass will be run instead.
-               See --simplify-instructions.
+               See --simplify-instructions.)");
+  printf(R"(
   --eliminate-local-multi-store
                Replace stores and loads of function scope variables that are
                stored multiple times. Performed on variables referenceed only
                with loads and stores. Performed only on entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --eliminate-local-single-block
                Perform single-block store/load and load/load elimination.
                Performed only on function scope variables in entry point
-               call tree functions.
+               call tree functions.)");
+  printf(R"(
   --eliminate-local-single-store
                Replace stores and loads of function scope variables that are
                only stored once. Performed on variables referenceed only with
                loads and stores. Performed only on entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --flatten-decorations
                Replace decoration groups with repeated OpDecorate and
-               OpMemberDecorate instructions.
+               OpMemberDecorate instructions.)");
+  printf(R"(
   --fold-spec-const-op-composite
                Fold the spec constants defined by OpSpecConstantOp or
                OpSpecConstantComposite instructions to front-end constants
-               when possible.
+               when possible.)");
+  printf(R"(
   --freeze-spec-const
                Freeze the values of specialization constants to their default
-               values.
+               values.)");
+  printf(R"(
+  --generate-webgpu-initializers
+               Adds initial values to OpVariable instructions that are missing
+               them, due to their storage type requiring them for WebGPU.)");
+  printf(R"(
   --if-conversion
-               Convert if-then-else like assignments into OpSelect.
+               Convert if-then-else like assignments into OpSelect.)");
+  printf(R"(
   --inline-entry-points-exhaustive
                Exhaustively inline all function calls in entry point call tree
                functions. Currently does not inline calls to functions with
-               early return in a loop.
+               early return in a loop.)");
+  printf(R"(
   --legalize-hlsl
                Runs a series of optimizations that attempts to take SPIR-V
                generated by an HLSL front-end and generates legal Vulkan SPIR-V.
@@ -191,46 +225,63 @@
                %s
 
                Note this does not guarantee legal code. This option passes the
-               option --relax-logical-pointer to the validator.
+               option --relax-logical-pointer to the validator.)",
+         GetLegalizationPasses().c_str());
+  printf(R"(
+  --legalize-vector-shuffle
+               Converts any usages of 0xFFFFFFFF for the literals in
+               OpVectorShuffle to a literal 0. This is done since 0xFFFFFFFF is
+               forbidden in WebGPU.)");
+  printf(R"(
   --local-redundancy-elimination
                Looks for instructions in the same basic block that compute the
-               same value, and deletes the redundant ones.
+               same value, and deletes the redundant ones.)");
+  printf(R"(
   --loop-fission
                Splits any top level loops in which the register pressure has
                exceeded a given threshold. The threshold must follow the use of
-               this flag and must be a positive integer value.
+               this flag and must be a positive integer value.)");
+  printf(R"(
   --loop-fusion
                Identifies adjacent loops with the same lower and upper bound.
                If this is legal, then merge the loops into a single loop.
                Includes heuristics to ensure it does not increase number of
                registers too much, while reducing the number of loads from
                memory. Takes an additional positive integer argument to set
-               the maximum number of registers.
+               the maximum number of registers.)");
+  printf(R"(
   --loop-invariant-code-motion
                Identifies code in loops that has the same value for every
-               iteration of the loop, and move it to the loop pre-header.
+               iteration of the loop, and move it to the loop pre-header.)");
+  printf(R"(
   --loop-unroll
-               Fully unrolls loops marked with the Unroll flag
+               Fully unrolls loops marked with the Unroll flag)");
+  printf(R"(
   --loop-unroll-partial
                Partially unrolls loops marked with the Unroll flag. Takes an
                additional non-0 integer argument to set the unroll factor, or
-               how many times a loop body should be duplicated
+               how many times a loop body should be duplicated)");
+  printf(R"(
   --loop-peeling
                Execute few first (respectively last) iterations before
-               (respectively after) the loop if it can elide some branches.
+               (respectively after) the loop if it can elide some branches.)");
+  printf(R"(
   --loop-peeling-threshold
                Takes a non-0 integer argument to set the loop peeling code size
                growth threshold. The threshold prevents the loop peeling
                from happening if the code size increase created by
-               the optimization is above the threshold.
+               the optimization is above the threshold.)");
+  printf(R"(
   --max-id-bound=<n>
                Sets the maximum value for the id bound for the moudle.  The
                default is the minimum value for this limit, 0x3FFFFF.  See
-               section 2.17 of the Spir-V specification.
+               section 2.17 of the Spir-V specification.)");
+  printf(R"(
   --merge-blocks
                Join two blocks into a single block if the second has the
                first as its only predecessor. Performed only on entry point
-               call tree functions.
+               call tree functions.)");
+  printf(R"(
   --merge-return
                Changes functions that have multiple return statements so they
                have a single return statement.
@@ -244,17 +295,21 @@
                label and an OpBranch to the header, nothing else.
 
                These conditions are guaranteed to be met after running
-               dead-branch elimination.
+               dead-branch elimination.)");
+  printf(R"(
   --loop-unswitch
                Hoists loop-invariant conditionals out of loops by duplicating
                the loop on each branch of the conditional and adjusting each
-               copy of the loop.
+               copy of the loop.)");
+  printf(R"(
   -O
                Optimize for performance. Apply a sequence of transformations
                in an attempt to improve the performance of the generated
                code. For this version of the optimizer, this flag is equivalent
                to specifying the following optimization code names:
-               %s
+               %s)",
+         GetOptimizationPasses().c_str());
+  printf(R"(
   -Os
                Optimize for size. Apply a sequence of transformations in an
                attempt to minimize the size of the generated code. For this
@@ -263,7 +318,9 @@
                %s
 
                NOTE: The specific transformations done by -O and -Os change
-                     from release to release.
+                     from release to release.)",
+         GetSizePasses().c_str());
+  printf(R"(
   -Oconfig=<file>
                Apply the sequence of transformations indicated in <file>.
                This file contains a sequence of strings separated by whitespace
@@ -291,105 +348,163 @@
                that position in the command line. For example, the invocation
                'spirv-opt --merge-blocks -O ...' applies the transformation
                --merge-blocks followed by all the transformations implied by
-               -O.
+               -O.)");
+  printf(R"(
+  --preserve-bindings
+               Ensure that the optimizer preserves all bindings declared within
+               the module, even when those bindings are unused.)");
+  printf(R"(
+  --preserve-spec-constants
+               Ensure that the optimizer preserves all specialization constants declared
+               within the module, even when those constants are unused.)");
+  printf(R"(
   --print-all
                Print SPIR-V assembly to standard error output before each pass
-               and after the last pass.
+               and after the last pass.)");
+  printf(R"(
   --private-to-local
                Change the scope of private variables that are used in a single
-               function to that function.
+               function to that function.)");
+  printf(R"(
   --reduce-load-size
                Replaces loads of composite objects where not every component is
-               used by loads of just the elements that are used.
+               used by loads of just the elements that are used.)");
+  printf(R"(
   --redundancy-elimination
                Looks for instructions in the same function that compute the
-               same value, and deletes the redundant ones.
+               same value, and deletes the redundant ones.)");
+  printf(R"(
   --relax-struct-store
                Allow store from one struct type to a different type with
                compatible layout and members. This option is forwarded to the
-               validator.
+               validator.)");
+  printf(R"(
   --remove-duplicates
                Removes duplicate types, decorations, capabilities and extension
-               instructions.
+               instructions.)");
+  printf(R"(
   --replace-invalid-opcode
                Replaces instructions whose opcode is valid for shader modules,
                but not for the current shader stage.  To have an effect, all
-               entry points must have the same execution model.
+               entry points must have the same execution model.)");
+  printf(R"(
   --ssa-rewrite
                Replace loads and stores to function local variables with
-               operations on SSA IDs.
+               operations on SSA IDs.)");
+  printf(R"(
   --scalar-replacement[=<n>]
                Replace aggregate function scope variables that are only accessed
                via their elements with new function variables representing each
                element.  <n> is a limit on the size of the aggragates that will
                be replaced.  0 means there is no limit.  The default value is
-               100.
+               100.)");
+  printf(R"(
   --set-spec-const-default-value "<spec id>:<default value> ..."
                Set the default values of the specialization constants with
                <spec id>:<default value> pairs specified in a double-quoted
                string. <spec id>:<default value> pairs must be separated by
                blank spaces, and in each pair, spec id and default value must
                be separated with colon ':' without any blank spaces in between.
-               e.g.: --set-spec-const-default-value "1:100 2:400"
+               e.g.: --set-spec-const-default-value "1:100 2:400")");
+  printf(R"(
   --simplify-instructions
                Will simplify all instructions in the function as much as
-               possible.
+               possible.)");
+  printf(R"(
+  --split-invalid-unreachable
+               Attempts to legalize for WebGPU cases where an unreachable
+               merge-block is also a continue-target by splitting it into two
+               seperate blocks. There exist legal, for Vulkan, instances of this
+               pattern that cannot be converted into legal WebGPU, so this
+               conversion may not succeed.)");
+  printf(R"(
   --skip-validation
                Will not validate the SPIR-V before optimizing.  If the SPIR-V
                is invalid, the optimizer may fail or generate incorrect code.
-               This options should be used rarely, and with caution.
+               This options should be used rarely, and with caution.)");
+  printf(R"(
   --strength-reduction
-               Replaces instructions with equivalent and less expensive ones.
+               Replaces instructions with equivalent and less expensive ones.)");
+  printf(R"(
+  --strip-atomic-counter-memory
+               Removes AtomicCountMemory bit from memory semantics values.)");
+  printf(R"(
   --strip-debug
-               Remove all debug instructions.
+               Remove all debug instructions.)");
+  printf(R"(
   --strip-reflect
                Remove all reflection information.  For now, this covers
-               reflection information defined by SPV_GOOGLE_hlsl_functionality1.
+               reflection information defined by SPV_GOOGLE_hlsl_functionality1.)");
+  printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
-               enviroment defaults to spv1.3.
-               <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
-               spv1.1, spv1.2, spv1.3, or webgpu0.
+               enviroment defaults to spv1.3. <env> must be one of
+               {%s})",
+         target_env_list.c_str());
+  printf(R"(
   --time-report
                Print the resource utilization of each pass (e.g., CPU time,
                RSS) to standard error output. Currently it supports only Unix
                systems. This option is the same as -ftime-report in GCC. It
                prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
                USR/SYS time are returned by getrusage() and can have a small
-               error.
+               error.)");
+  printf(R"(
   --upgrade-memory-model
                Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
                Transforms memory, image, atomic and barrier operations to conform
-               to that model's requirements.
+               to that model's requirements.)");
+  printf(R"(
   --vector-dce
                This pass looks for components of vectors that are unused, and
                removes them from the vector.  Note this would still leave around
-               lots of dead code that a pass of ADCE will be able to remove.
-  --webgpu-mode
-               Turns on the prescribed passes for WebGPU and sets the target
-               environmet to webgpu0. Other passes may be turned on via
-               additional flags, but such combinations are not tested.
+               lots of dead code that a pass of ADCE will be able to remove.)");
+  printf(R"(
+  --vulkan-to-webgpu
+               Turns on the prescribed passes for converting from Vulkan to
+               WebGPU and sets the target environment to webgpu0. Other passes
+               may be turned on via additional flags, but such combinations are
+               not tested.
                Using --target-env with this flag is not allowed.
 
                This flag is the equivalent of passing in --target-env=webgpu0
                and specifying the following optimization code names:
                %s
 
-               NOTE: This flag is a WIP and its behaviour is subject to change.
+               NOTE: This flag is a WIP and its behaviour is subject to change.)",
+         GetVulkanToWebGPUPasses().c_str());
+  printf(R"(
+  --webgpu-to-vulkan
+               Turns on the prescribed passes for converting from WebGPU to
+               Vulkan and sets the target environment to vulkan1.1. Other passes
+               may be turned on via additional flags, but such combinations are
+               not tested.
+               Using --target-env with this flag is not allowed.
+
+               This flag is the equivalent of passing in --target-env=vulkan1.1
+               and specifying the following optimization code names:
+               %s
+
+               NOTE: This flag is a WIP and its behaviour is subject to change.)",
+         GetWebGPUToVulkanPasses().c_str());
+  printf(R"(
   --workaround-1209
                Rewrites instructions for which there are known driver bugs to
                avoid triggering those bugs.
-               Current workarounds: Avoid OpUnreachable in loops.
+               Current workarounds: Avoid OpUnreachable in loops.)");
+  printf(R"(
   --unify-const
-               Remove the duplicated constants.
+               Remove the duplicated constants.)");
+  printf(R"(
+  --validate-after-all
+               Validate the module after each pass is performed.)");
+  printf(R"(
   -h, --help
-               Print this help.
+               Print this help.)");
+  printf(R"(
   --version
                Display optimizer version information.
-)",
-      program, program, GetLegalizationPasses().c_str(),
-      GetOptimizationPasses().c_str(), GetSizePasses().c_str(),
-      GetWebGPUPasses().c_str());
+)");
 }
 
 // Reads command-line flags  the file specified in |oconfig_flag|. This string
@@ -540,7 +655,8 @@
                      spvtools::OptimizerOptions* optimizer_options) {
   std::vector<std::string> pass_flags;
   bool target_env_set = false;
-  bool webgpu_mode_set = false;
+  bool vulkan_to_webgpu_set = false;
+  bool webgpu_to_vulkan_set = false;
   for (int argi = 1; argi < argc; ++argi) {
     const char* cur_arg = argv[argi];
     if ('-' == cur_arg[0]) {
@@ -578,6 +694,10 @@
         optimizer_options->set_run_validator(false);
       } else if (0 == strcmp(cur_arg, "--print-all")) {
         optimizer->SetPrintAll(&std::cerr);
+      } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
+        optimizer_options->set_preserve_bindings(true);
+      } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
+        optimizer_options->set_preserve_spec_constants(true);
       } else if (0 == strcmp(cur_arg, "--time-report")) {
         optimizer->SetTimeReport(&std::cerr);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
@@ -601,10 +721,17 @@
                                              max_id_bound);
       } else if (0 == strncmp(cur_arg,
                               "--target-env=", sizeof("--target-env=") - 1)) {
-        if (webgpu_mode_set) {
+        target_env_set = true;
+        if (vulkan_to_webgpu_set) {
           spvtools::Error(opt_diagnostic, nullptr, {},
-                          "Cannot use both --webgpu-mode and --target-env at "
-                          "the same time");
+                          "--vulkan-to-webgpu defines the target environment, "
+                          "so --target-env cannot be set at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (webgpu_to_vulkan_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "--webgpu-to-vulkan defines the target environment, "
+                          "so --target-env cannot be set at the same time");
           return {OPT_STOP, 1};
         }
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
@@ -616,16 +743,44 @@
           return {OPT_STOP, 1};
         }
         optimizer->SetTargetEnv(target_env);
-      } else if (0 == strcmp(cur_arg, "--webgpu-mode")) {
+      } else if (0 == strcmp(cur_arg, "--vulkan-to-webgpu")) {
+        vulkan_to_webgpu_set = true;
         if (target_env_set) {
           spvtools::Error(opt_diagnostic, nullptr, {},
-                          "Cannot use both --webgpu-mode and --target-env at "
-                          "the same time");
+                          "--vulkan-to-webgpu defines the target environment, "
+                          "so --target-env cannot be set at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (webgpu_to_vulkan_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --webgpu-to-vulkan and "
+                          "--vulkan-to-webgpu at the same time, invoke twice "
+                          "if you are wanting to go to and from");
           return {OPT_STOP, 1};
         }
 
         optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0);
-        optimizer->RegisterWebGPUPasses();
+        optimizer->RegisterVulkanToWebGPUPasses();
+      } else if (0 == strcmp(cur_arg, "--webgpu-to-vulkan")) {
+        webgpu_to_vulkan_set = true;
+        if (target_env_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "--webgpu-to-vulkan defines the target environment, "
+                          "so --target-env cannot be set at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (vulkan_to_webgpu_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --webgpu-to-vulkan and "
+                          "--vulkan-to-webgpu at the same time, invoke twice "
+                          "if you are wanting to go to and from");
+          return {OPT_STOP, 1};
+        }
+
+        optimizer->SetTargetEnv(SPV_ENV_VULKAN_1_1);
+        optimizer->RegisterWebGPUToVulkanPasses();
+      } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
+        optimizer->SetValidateAfterAll(true);
       } else {
         // Some passes used to accept the form '--pass arg', canonicalize them
         // to '--pass=arg'.
@@ -634,7 +789,7 @@
         // If we were requested to legalize SPIR-V generated from the HLSL
         // front-end, skip validation.
         if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
-          validator_options->SetRelaxLogicalPointer(true);
+          validator_options->SetBeforeHlslLegalization(true);
         }
       }
     } else {
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 65325f7..be07b20 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -20,26 +20,14 @@
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/log.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
-#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
-#include "source/reduce/operand_to_undef_reduction_pass.h"
 #include "source/reduce/reducer.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
-#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
 #include "source/spirv_reducer_options.h"
-#include "source/util/make_unique.h"
 #include "source/util/string_utils.h"
-#include "spirv-tools/libspirv.hpp"
 #include "tools/io.h"
 #include "tools/util/cli_consumer.h"
 
-using namespace spvtools::reduce;
-
 namespace {
 
-using ErrorOrInt = std::pair<std::string, int>;
-
 // Check that the std::system function can actually be used.
 bool CheckExecuteCommand() {
   int res = std::system(nullptr);
@@ -77,18 +65,49 @@
 The SPIR-V binary is read from <input>.
 
 Whether a binary is interesting is determined by <interestingness-test>, which
-is typically a script.
+should be the path to a script.
+
+ * The script must be executable.
+
+ * The script should take the path to a SPIR-V binary file (.spv) as its single
+   argument, and exit with code 0 if and only if the binary file is
+   interesting.
+
+ * Example: an interestingness test for reducing a SPIR-V binary file that
+   causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
+   standard error should:
+     - invoke "foo" on the binary passed as the script argument;
+     - capture the return code and standard error from "bar";
+     - exit with code 0 if and only if the return code of "foo" was 1 and the
+       standard error from "bar" contained "Fatal error: bar".
+
+ * The reducer does not place a time limit on how long the interestingness test
+   takes to run, so it is advisable to use per-command timeouts inside the
+   script when invoking SPIR-V-processing tools (such as "foo" in the above
+   example).
 
 NOTE: The reducer is a work in progress.
 
 Options (in lexicographical order):
+
+  --fail-on-validation-error
+               Stop reduction with an error if any reduction step produces a
+               SPIR-V module that fails to validate.
   -h, --help
                Print this help.
   --step-limit
-               32-bit unsigned integer specifying maximum number of
-               steps the reducer will take before giving up.
+               32-bit unsigned integer specifying maximum number of steps the
+               reducer will take before giving up.
   --version
                Display reducer version information.
+
+Supported validator options are as follows. See `spirv-val --help` for details.
+  --before-hlsl-legalization
+  --relax-block-layout
+  --relax-logical-pointer
+  --relax-struct-store
+  --scalar-block-layout
+  --skip-block-layout
 )",
       program, program);
 }
@@ -106,7 +125,8 @@
 
 ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
                         const char** interestingness_test,
-                        spvtools::ReducerOptions* reducer_options) {
+                        spvtools::ReducerOptions* reducer_options,
+                        spvtools::ValidatorOptions* validator_options) {
   uint32_t positional_arg_index = 0;
 
   for (int argi = 1; argi < argc; ++argi) {
@@ -143,6 +163,20 @@
       assert(!*interestingness_test);
       *interestingness_test = cur_arg;
       positional_arg_index++;
+    } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
+      reducer_options->set_fail_on_validation_error(true);
+    } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
+      validator_options->SetBeforeHlslLegalization(true);
+    } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
+      validator_options->SetRelaxLogicalPointer(true);
+    } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
+      validator_options->SetRelaxBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+      validator_options->SetScalarBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
+      validator_options->SetSkipBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
+      validator_options->SetRelaxStructStore(true);
     } else {
       spvtools::Error(ReduceDiagnostic, nullptr, {},
                       "Too many positional arguments specified");
@@ -166,7 +200,24 @@
 
 }  // namespace
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+// Dumps |binary| to file |filename|. Useful for interactive debugging.
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
+  auto write_file_succeeded =
+      WriteFile(filename, "wb", &binary[0], binary.size());
+  if (!write_file_succeeded) {
+    std::cerr << "Failed to dump shader" << std::endl;
+  }
+}
+
+// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
+// interactive debugging.
+void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  DumpShader(binary, filename);
+}
+
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, const char** argv) {
   const char* in_file = nullptr;
@@ -174,9 +225,10 @@
 
   spv_target_env target_env = kDefaultEnvironment;
   spvtools::ReducerOptions reducer_options;
+  spvtools::ValidatorOptions validator_options;
 
-  ReduceStatus status =
-      ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options);
+  ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
+                                   &reducer_options, &validator_options);
 
   if (status.action == REDUCE_STOP) {
     return status.code;
@@ -188,7 +240,7 @@
     return 2;
   }
 
-  Reducer reducer(target_env);
+  spvtools::reduce::Reducer reducer(target_env);
 
   reducer.SetInterestingnessFunction(
       [interestingness_test](std::vector<uint32_t> binary,
@@ -206,19 +258,7 @@
         return ExecuteCommand(command);
       });
 
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<OperandToUndefReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
-          target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
+  reducer.AddDefaultReductionPasses();
 
   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
@@ -228,11 +268,11 @@
   }
 
   std::vector<uint32_t> binary_out;
-  const auto reduction_status =
-      reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
+                                            reducer_options, validator_options);
 
-  if (reduction_status ==
-          Reducer::ReductionResultStatus::kInitialStateNotInteresting ||
+  if (reduction_status == spvtools::reduce::Reducer::ReductionResultStatus::
+                              kInitialStateNotInteresting ||
       !WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
                            binary_out.size())) {
     return 1;
diff --git a/tools/stats/spirv_stats.cpp b/tools/stats/spirv_stats.cpp
deleted file mode 100644
index 609a6c9..0000000
--- a/tools/stats/spirv_stats.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-// 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 "tools/stats/spirv_stats.h"
-
-#include <cassert>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-
-#include "source/diagnostic.h"
-#include "source/enum_string_mapping.h"
-#include "source/extensions.h"
-#include "source/id_descriptor.h"
-#include "source/instruction.h"
-#include "source/opcode.h"
-#include "source/operand.h"
-#include "source/val/instruction.h"
-#include "source/val/validate.h"
-#include "source/val/validation_state.h"
-#include "spirv-tools/libspirv.h"
-
-namespace spvtools {
-namespace stats {
-namespace {
-
-// Helper class for stats aggregation. Receives as in/out parameter.
-// Constructs ValidationState and updates it by running validator for each
-// instruction.
-class StatsAggregator {
- public:
-  StatsAggregator(SpirvStats* in_out_stats, const val::ValidationState_t* state)
-      : stats_(in_out_stats), vstate_(state) {}
-
-  // Processes the instructions to collect stats.
-  void aggregate() {
-    const auto& instructions = vstate_->ordered_instructions();
-
-    ++stats_->version_hist[vstate_->version()];
-    ++stats_->generator_hist[vstate_->generator()];
-
-    for (size_t i = 0; i < instructions.size(); ++i) {
-      const auto& inst = instructions[i];
-
-      ProcessOpcode(&inst, i);
-      ProcessCapability(&inst);
-      ProcessExtension(&inst);
-      ProcessConstant(&inst);
-    }
-  }
-
-  // Collects OpCapability statistics.
-  void ProcessCapability(const val::Instruction* inst) {
-    if (inst->opcode() != SpvOpCapability) return;
-    const uint32_t capability = inst->word(inst->operands()[0].offset);
-    ++stats_->capability_hist[capability];
-  }
-
-  // Collects OpExtension statistics.
-  void ProcessExtension(const val::Instruction* inst) {
-    if (inst->opcode() != SpvOpExtension) return;
-    const std::string extension = GetExtensionString(&inst->c_inst());
-    ++stats_->extension_hist[extension];
-  }
-
-  // Collects OpCode statistics.
-  void ProcessOpcode(const val::Instruction* inst, size_t idx) {
-    const SpvOp opcode = inst->opcode();
-    ++stats_->opcode_hist[opcode];
-
-    if (idx == 0) return;
-
-    --idx;
-
-    const auto& instructions = vstate_->ordered_instructions();
-
-    auto step_it = stats_->opcode_markov_hist.begin();
-    for (; step_it != stats_->opcode_markov_hist.end(); --idx, ++step_it) {
-      auto& hist = (*step_it)[instructions[idx].opcode()];
-      ++hist[opcode];
-
-      if (idx == 0) break;
-    }
-  }
-
-  // Collects OpConstant statistics.
-  void ProcessConstant(const val::Instruction* inst) {
-    if (inst->opcode() != SpvOpConstant) return;
-
-    const uint32_t type_id = inst->GetOperandAs<uint32_t>(0);
-    const auto type_decl_it = vstate_->all_definitions().find(type_id);
-    assert(type_decl_it != vstate_->all_definitions().end());
-
-    const val::Instruction& type_decl_inst = *type_decl_it->second;
-    const SpvOp type_op = type_decl_inst.opcode();
-    if (type_op == SpvOpTypeInt) {
-      const uint32_t bit_width = type_decl_inst.GetOperandAs<uint32_t>(1);
-      const uint32_t is_signed = type_decl_inst.GetOperandAs<uint32_t>(2);
-      assert(is_signed == 0 || is_signed == 1);
-      if (bit_width == 16) {
-        if (is_signed)
-          ++stats_->s16_constant_hist[inst->GetOperandAs<int16_t>(2)];
-        else
-          ++stats_->u16_constant_hist[inst->GetOperandAs<uint16_t>(2)];
-      } else if (bit_width == 32) {
-        if (is_signed)
-          ++stats_->s32_constant_hist[inst->GetOperandAs<int32_t>(2)];
-        else
-          ++stats_->u32_constant_hist[inst->GetOperandAs<uint32_t>(2)];
-      } else if (bit_width == 64) {
-        if (is_signed)
-          ++stats_->s64_constant_hist[inst->GetOperandAs<int64_t>(2)];
-        else
-          ++stats_->u64_constant_hist[inst->GetOperandAs<uint64_t>(2)];
-      } else {
-        assert(false && "TypeInt bit width is not 16, 32 or 64");
-      }
-    } else if (type_op == SpvOpTypeFloat) {
-      const uint32_t bit_width = type_decl_inst.GetOperandAs<uint32_t>(1);
-      if (bit_width == 32) {
-        ++stats_->f32_constant_hist[inst->GetOperandAs<float>(2)];
-      } else if (bit_width == 64) {
-        ++stats_->f64_constant_hist[inst->GetOperandAs<double>(2)];
-      } else {
-        assert(bit_width == 16);
-      }
-    }
-  }
-
- private:
-  SpirvStats* stats_;
-  const val::ValidationState_t* vstate_;
-  IdDescriptorCollection id_descriptors_;
-};
-
-}  // namespace
-
-spv_result_t AggregateStats(const spv_context context, const uint32_t* words,
-                            const size_t num_words, spv_diagnostic* pDiagnostic,
-                            SpirvStats* stats) {
-  std::unique_ptr<val::ValidationState_t> vstate;
-  spv_validator_options_t options;
-  spv_result_t result = ValidateBinaryAndKeepValidationState(
-      context, &options, words, num_words, pDiagnostic, &vstate);
-  if (result != SPV_SUCCESS) return result;
-
-  StatsAggregator stats_aggregator(stats, vstate.get());
-  stats_aggregator.aggregate();
-  return SPV_SUCCESS;
-}
-
-}  // namespace stats
-}  // namespace spvtools
diff --git a/tools/stats/spirv_stats.h b/tools/stats/spirv_stats.h
deleted file mode 100644
index 7576957..0000000
--- a/tools/stats/spirv_stats.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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.
-
-#ifndef TOOLS_STATS_SPIRV_STATS_H_
-#define TOOLS_STATS_SPIRV_STATS_H_
-
-#include <map>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "spirv-tools/libspirv.hpp"
-
-namespace spvtools {
-namespace stats {
-
-struct SpirvStats {
-  // Version histogram, version_word -> count.
-  std::unordered_map<uint32_t, uint32_t> version_hist;
-
-  // Generator histogram, generator_word -> count.
-  std::unordered_map<uint32_t, uint32_t> generator_hist;
-
-  // Capability histogram, SpvCapabilityXXX -> count.
-  std::unordered_map<uint32_t, uint32_t> capability_hist;
-
-  // Extension histogram, extension_string -> count.
-  std::unordered_map<std::string, uint32_t> extension_hist;
-
-  // Opcode histogram, SpvOpXXX -> count.
-  std::unordered_map<uint32_t, uint32_t> opcode_hist;
-
-  // OpConstant u16 histogram, value -> count.
-  std::unordered_map<uint16_t, uint32_t> u16_constant_hist;
-
-  // OpConstant u32 histogram, value -> count.
-  std::unordered_map<uint32_t, uint32_t> u32_constant_hist;
-
-  // OpConstant u64 histogram, value -> count.
-  std::unordered_map<uint64_t, uint32_t> u64_constant_hist;
-
-  // OpConstant s16 histogram, value -> count.
-  std::unordered_map<int16_t, uint32_t> s16_constant_hist;
-
-  // OpConstant s32 histogram, value -> count.
-  std::unordered_map<int32_t, uint32_t> s32_constant_hist;
-
-  // OpConstant s64 histogram, value -> count.
-  std::unordered_map<int64_t, uint32_t> s64_constant_hist;
-
-  // OpConstant f32 histogram, value -> count.
-  std::unordered_map<float, uint32_t> f32_constant_hist;
-
-  // OpConstant f64 histogram, value -> count.
-  std::unordered_map<double, uint32_t> f64_constant_hist;
-
-  // Used to collect statistics on opcodes triggering other opcodes.
-  // Container scheme: gap between instructions -> cue opcode -> later opcode
-  // -> count.
-  // For example opcode_markov_hist[2][OpFMul][OpFAdd] corresponds to
-  // the number of times an OpMul appears, followed by 2 other instructions,
-  // followed by OpFAdd.
-  // opcode_markov_hist[0][OpFMul][OpFAdd] corresponds to how many times
-  // OpFMul appears, directly followed by OpFAdd.
-  // The size of the outer std::vector also serves as an input parameter,
-  // determining how many steps will be collected.
-  // I.e. do opcode_markov_hist.resize(1) to collect data for one step only.
-  std::vector<
-      std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>>
-      opcode_markov_hist;
-};
-
-// Aggregates existing |stats| with new stats extracted from |binary|.
-spv_result_t AggregateStats(const spv_context context, const uint32_t* words,
-                            const size_t num_words, spv_diagnostic* pDiagnostic,
-                            SpirvStats* stats);
-
-}  // namespace stats
-}  // namespace spvtools
-
-#endif  // TOOLS_STATS_SPIRV_STATS_H_
diff --git a/tools/stats/stats.cpp b/tools/stats/stats.cpp
deleted file mode 100644
index 30e3bcc..0000000
--- a/tools/stats/stats.cpp
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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 <cassert>
-#include <cstring>
-#include <fstream>
-#include <iostream>
-#include <unordered_map>
-#include <vector>
-
-#include "spirv-tools/libspirv.h"
-#include "tools/io.h"
-#include "tools/stats/spirv_stats.h"
-#include "tools/stats/stats_analyzer.h"
-
-namespace {
-
-void PrintUsage(char* argv0) {
-  printf(
-      R"(%s - Collect statistics from one or more SPIR-V binary file(s).
-
-USAGE: %s [options] [<filepaths>]
-
-TIP: In order to collect statistics from all .spv files under current dir use
-find . -name "*.spv" -print0 | xargs -0 -s 2000000 %s
-
-Options:
-  -h, --help
-                   Print this help.
-
-  -v, --verbose
-                   Print additional info to stderr.
-)",
-      argv0, argv0, argv0);
-}
-
-void DiagnosticsMessageHandler(spv_message_level_t level, const char*,
-                               const spv_position_t& position,
-                               const char* message) {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: " << position.index << ": " << message << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cout << "warning: " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cout << "info: " << position.index << ": " << message << std::endl;
-      break;
-    default:
-      break;
-  }
-}
-
-}  // namespace
-
-int main(int argc, char** argv) {
-  bool continue_processing = true;
-  int return_code = 0;
-
-  bool expect_output_path = false;
-  bool verbose = false;
-
-  std::vector<const char*> paths;
-  const char* output_path = nullptr;
-
-  for (int argi = 1; continue_processing && argi < argc; ++argi) {
-    const char* cur_arg = argv[argi];
-    if ('-' == cur_arg[0]) {
-      if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
-        PrintUsage(argv[0]);
-        continue_processing = false;
-        return_code = 0;
-      } else if (0 == strcmp(cur_arg, "--verbose") ||
-                 0 == strcmp(cur_arg, "-v")) {
-        verbose = true;
-      } else if (0 == strcmp(cur_arg, "--output") ||
-                 0 == strcmp(cur_arg, "-o")) {
-        expect_output_path = true;
-      } else {
-        PrintUsage(argv[0]);
-        continue_processing = false;
-        return_code = 1;
-      }
-    } else {
-      if (expect_output_path) {
-        output_path = cur_arg;
-        expect_output_path = false;
-      } else {
-        paths.push_back(cur_arg);
-      }
-    }
-  }
-
-  // Exit if command line parsing was not successful.
-  if (!continue_processing) {
-    return return_code;
-  }
-
-  std::cerr << "Processing " << paths.size() << " files..." << std::endl;
-
-  spvtools::Context ctx(SPV_ENV_UNIVERSAL_1_1);
-  ctx.SetMessageConsumer(DiagnosticsMessageHandler);
-
-  spvtools::stats::SpirvStats stats;
-  stats.opcode_markov_hist.resize(1);
-
-  for (size_t index = 0; index < paths.size(); ++index) {
-    const size_t kMilestonePeriod = 1000;
-    if (verbose) {
-      if (index % kMilestonePeriod == kMilestonePeriod - 1)
-        std::cerr << "Processed " << index + 1 << " files..." << std::endl;
-    }
-
-    const char* path = paths[index];
-    std::vector<uint32_t> contents;
-    if (!ReadFile<uint32_t>(path, "rb", &contents)) return 1;
-
-    if (SPV_SUCCESS !=
-        spvtools::stats::AggregateStats(ctx.CContext(), contents.data(),
-                                        contents.size(), nullptr, &stats)) {
-      std::cerr << "error: Failed to aggregate stats for " << path << std::endl;
-      return 1;
-    }
-  }
-
-  spvtools::stats::StatsAnalyzer analyzer(stats);
-
-  std::ofstream fout;
-  if (output_path) {
-    fout.open(output_path);
-    if (!fout.is_open()) {
-      std::cerr << "error: Failed to open " << output_path << std::endl;
-      return 1;
-    }
-  }
-
-  std::ostream& out = fout.is_open() ? fout : std::cout;
-  out << std::endl;
-  analyzer.WriteVersion(out);
-  analyzer.WriteGenerator(out);
-
-  out << std::endl;
-  analyzer.WriteCapability(out);
-
-  out << std::endl;
-  analyzer.WriteExtension(out);
-
-  out << std::endl;
-  analyzer.WriteOpcode(out);
-
-  out << std::endl;
-  analyzer.WriteOpcodeMarkov(out);
-
-  out << std::endl;
-  analyzer.WriteConstantLiterals(out);
-
-  return 0;
-}
diff --git a/tools/stats/stats_analyzer.cpp b/tools/stats/stats_analyzer.cpp
deleted file mode 100644
index 6d4cabb..0000000
--- a/tools/stats/stats_analyzer.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-// 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 "tools/stats/stats_analyzer.h"
-
-#include <algorithm>
-#include <cassert>
-#include <cstring>
-#include <iostream>
-#include <map>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "source/comp/markv_model.h"
-#include "source/enum_string_mapping.h"
-#include "source/latest_version_spirv_header.h"
-#include "source/opcode.h"
-#include "source/operand.h"
-#include "source/spirv_constant.h"
-
-namespace spvtools {
-namespace stats {
-namespace {
-
-// Signals that the value is not in the coding scheme and a fallback method
-// needs to be used.
-const uint64_t kMarkvNoneOfTheAbove =
-    comp::MarkvModel::GetMarkvNoneOfTheAbove();
-
-std::string GetVersionString(uint32_t word) {
-  std::stringstream ss;
-  ss << "Version " << SPV_SPIRV_VERSION_MAJOR_PART(word) << "."
-     << SPV_SPIRV_VERSION_MINOR_PART(word);
-  return ss.str();
-}
-
-std::string GetGeneratorString(uint32_t word) {
-  return spvGeneratorStr(SPV_GENERATOR_TOOL_PART(word));
-}
-
-std::string GetOpcodeString(uint32_t word) {
-  return spvOpcodeString(static_cast<SpvOp>(word));
-}
-
-std::string GetCapabilityString(uint32_t word) {
-  return CapabilityToString(static_cast<SpvCapability>(word));
-}
-
-template <class T>
-std::string KeyIsLabel(T key) {
-  std::stringstream ss;
-  ss << key;
-  return ss.str();
-}
-
-template <class Key>
-std::unordered_map<Key, double> GetRecall(
-    const std::unordered_map<Key, uint32_t>& hist, uint64_t total) {
-  std::unordered_map<Key, double> freq;
-  for (const auto& pair : hist) {
-    const double frequency =
-        static_cast<double>(pair.second) / static_cast<double>(total);
-    freq.emplace(pair.first, frequency);
-  }
-  return freq;
-}
-
-template <class Key>
-std::unordered_map<Key, double> GetPrevalence(
-    const std::unordered_map<Key, uint32_t>& hist) {
-  uint64_t total = 0;
-  for (const auto& pair : hist) {
-    total += pair.second;
-  }
-
-  return GetRecall(hist, total);
-}
-
-// Writes |freq| to |out| sorted by frequency in the following format:
-// LABEL3 70%
-// LABEL1 20%
-// LABEL2 10%
-// |label_from_key| is used to convert |Key| to label.
-template <class Key>
-void WriteFreq(std::ostream& out, const std::unordered_map<Key, double>& freq,
-               std::string (*label_from_key)(Key)) {
-  std::vector<std::pair<Key, double>> sorted_freq(freq.begin(), freq.end());
-  std::sort(sorted_freq.begin(), sorted_freq.end(),
-            [](const std::pair<Key, double>& left,
-               const std::pair<Key, double>& right) {
-              return left.second > right.second;
-            });
-
-  for (const auto& pair : sorted_freq) {
-    if (pair.second < 0.001) break;
-    out << label_from_key(pair.first) << " " << pair.second * 100.0 << "%"
-        << std::endl;
-  }
-}
-
-}  // namespace
-
-StatsAnalyzer::StatsAnalyzer(const SpirvStats& stats) : stats_(stats) {
-  num_modules_ = 0;
-  for (const auto& pair : stats_.version_hist) {
-    num_modules_ += pair.second;
-  }
-
-  version_freq_ = GetRecall(stats_.version_hist, num_modules_);
-  generator_freq_ = GetRecall(stats_.generator_hist, num_modules_);
-  capability_freq_ = GetRecall(stats_.capability_hist, num_modules_);
-  extension_freq_ = GetRecall(stats_.extension_hist, num_modules_);
-  opcode_freq_ = GetPrevalence(stats_.opcode_hist);
-}
-
-void StatsAnalyzer::WriteVersion(std::ostream& out) {
-  WriteFreq(out, version_freq_, GetVersionString);
-}
-
-void StatsAnalyzer::WriteGenerator(std::ostream& out) {
-  WriteFreq(out, generator_freq_, GetGeneratorString);
-}
-
-void StatsAnalyzer::WriteCapability(std::ostream& out) {
-  WriteFreq(out, capability_freq_, GetCapabilityString);
-}
-
-void StatsAnalyzer::WriteExtension(std::ostream& out) {
-  WriteFreq(out, extension_freq_, KeyIsLabel);
-}
-
-void StatsAnalyzer::WriteOpcode(std::ostream& out) {
-  out << "Total unique opcodes used: " << opcode_freq_.size() << std::endl;
-  WriteFreq(out, opcode_freq_, GetOpcodeString);
-}
-
-void StatsAnalyzer::WriteConstantLiterals(std::ostream& out) {
-  out << "Constant literals" << std::endl;
-
-  out << "Float 32" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.f32_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Float 64" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.f64_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Unsigned int 16" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.u16_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Signed int 16" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.s16_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Unsigned int 32" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.u32_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Signed int 32" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.s32_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Unsigned int 64" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.u64_constant_hist), KeyIsLabel);
-
-  out << std::endl << "Signed int 64" << std::endl;
-  WriteFreq(out, GetPrevalence(stats_.s64_constant_hist), KeyIsLabel);
-}
-
-void StatsAnalyzer::WriteOpcodeMarkov(std::ostream& out) {
-  if (stats_.opcode_markov_hist.empty()) return;
-
-  const std::unordered_map<uint32_t, std::unordered_map<uint32_t, uint32_t>>&
-      cue_to_hist = stats_.opcode_markov_hist[0];
-
-  // Sort by prevalence of the opcodes in opcode_freq_ (descending).
-  std::vector<std::pair<uint32_t, std::unordered_map<uint32_t, uint32_t>>>
-      sorted_cue_to_hist(cue_to_hist.begin(), cue_to_hist.end());
-  std::sort(
-      sorted_cue_to_hist.begin(), sorted_cue_to_hist.end(),
-      [this](const std::pair<uint32_t, std::unordered_map<uint32_t, uint32_t>>&
-                 left,
-             const std::pair<uint32_t, std::unordered_map<uint32_t, uint32_t>>&
-                 right) {
-        const double lf = opcode_freq_[left.first];
-        const double rf = opcode_freq_[right.first];
-        if (lf == rf) return right.first > left.first;
-        return lf > rf;
-      });
-
-  for (const auto& kv : sorted_cue_to_hist) {
-    const uint32_t cue = kv.first;
-    const double kFrequentEnoughToAnalyze = 0.0001;
-    if (opcode_freq_[cue] < kFrequentEnoughToAnalyze) continue;
-
-    const std::unordered_map<uint32_t, uint32_t>& hist = kv.second;
-
-    uint32_t total = 0;
-    for (const auto& pair : hist) {
-      total += pair.second;
-    }
-
-    std::vector<std::pair<uint32_t, uint32_t>> sorted_hist(hist.begin(),
-                                                           hist.end());
-    std::sort(sorted_hist.begin(), sorted_hist.end(),
-              [](const std::pair<uint32_t, uint32_t>& left,
-                 const std::pair<uint32_t, uint32_t>& right) {
-                if (left.second == right.second)
-                  return right.first > left.first;
-                return left.second > right.second;
-              });
-
-    for (const auto& pair : sorted_hist) {
-      const double prior = opcode_freq_[pair.first];
-      const double posterior =
-          static_cast<double>(pair.second) / static_cast<double>(total);
-      out << GetOpcodeString(cue) << " -> " << GetOpcodeString(pair.first)
-          << " " << posterior * 100 << "% (base rate " << prior * 100
-          << "%, pair occurrences " << pair.second << ")" << std::endl;
-    }
-  }
-}
-
-}  // namespace stats
-}  // namespace spvtools
diff --git a/tools/stats/stats_analyzer.h b/tools/stats/stats_analyzer.h
deleted file mode 100644
index f1c37bf..0000000
--- a/tools/stats/stats_analyzer.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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.
-
-#ifndef TOOLS_STATS_STATS_ANALYZER_H_
-#define TOOLS_STATS_STATS_ANALYZER_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "tools/stats/spirv_stats.h"
-
-namespace spvtools {
-namespace stats {
-
-class StatsAnalyzer {
- public:
-  explicit StatsAnalyzer(const SpirvStats& stats);
-
-  // Writes respective histograms to |out|.
-  void WriteVersion(std::ostream& out);
-  void WriteGenerator(std::ostream& out);
-  void WriteCapability(std::ostream& out);
-  void WriteExtension(std::ostream& out);
-  void WriteOpcode(std::ostream& out);
-  void WriteConstantLiterals(std::ostream& out);
-
-  // Writes first order Markov analysis to |out|.
-  // stats_.opcode_markov_hist needs to contain raw data for at least one
-  // level.
-  void WriteOpcodeMarkov(std::ostream& out);
-
- private:
-  const SpirvStats& stats_;
-
-  uint32_t num_modules_;
-
-  std::unordered_map<uint32_t, double> version_freq_;
-  std::unordered_map<uint32_t, double> generator_freq_;
-  std::unordered_map<uint32_t, double> capability_freq_;
-  std::unordered_map<std::string, double> extension_freq_;
-  std::unordered_map<uint32_t, double> opcode_freq_;
-};
-
-}  // namespace stats
-}  // namespace spvtools
-
-#endif  // TOOLS_STATS_STATS_ANALYZER_H_
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index 8b1d048..6a8542d 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -25,6 +25,7 @@
 #include "tools/util/cli_consumer.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(36, 105);
   printf(
       R"(%s - Validate a SPIR-V binary file.
 
@@ -51,6 +52,8 @@
   --relax-block-layout             Enable VK_KHR_relaxed_block_layout when checking standard
                                    uniform, storage buffer, and push constant layouts.
                                    This is the default when targeting Vulkan 1.1 or later.
+  --uniform-buffer-standard-layout Enable VK_KHR_uniform_buffer_standard_layout when checking standard
+                                   uniform buffer layouts.
   --scalar-block-layout            Enable VK_EXT_scalar_block_layout when checking standard
                                    uniform, storage buffer, and push constant layouts.  Scalar layout
                                    rules are more permissive than relaxed block layout so in effect
@@ -60,17 +63,18 @@
   --relax-struct-store             Allow store from one struct type to a
                                    different type with compatible layout and
                                    members.
+  --before-hlsl-legalization       Allows code patterns that are intended to be
+                                   fixed by spirv-opt's legalization passes.
   --version                        Display validator version information.
-  --target-env                     {vulkan1.0|vulkan1.1|opencl2.2|spv1.0|spv1.1|spv1.2|spv1.3|webgpu0}
-                                   Use Vulkan 1.0, Vulkan 1.1, OpenCL 2.2, SPIR-V 1.0,
-                                   SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3 or WIP WebGPU validation rules.
+  --target-env                     {%s}
+                                   Use validation rules from the specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
-  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_4;
   spvtools::ValidatorOptions options;
   bool continue_processing = true;
   int return_code = 0;
@@ -102,15 +106,19 @@
         }
       } else if (0 == strcmp(cur_arg, "--version")) {
         printf("%s\n", spvSoftwareVersionDetailsString());
-        printf("Targets:\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n",
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
-               spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
-               spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
-               spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
-               spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
+        printf(
+            "Targets:\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  "
+            "%s\n",
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_4),
+            spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_1_SPIRV_1_4),
+            spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
         continue_processing = false;
         return_code = 0;
       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
@@ -130,10 +138,14 @@
           continue_processing = false;
           return_code = 1;
         }
+      } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
+        options.SetBeforeHlslLegalization(true);
       } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
         options.SetRelaxLogicalPointer(true);
       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
         options.SetRelaxBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--uniform-buffer-standard-layout")) {
+        options.SetUniformBufferStandardLayout(true);
       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
         options.SetScalarBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
diff --git a/utils/check_code_format.sh b/utils/check_code_format.sh
index a6a5879..7994740 100755
--- a/utils/check_code_format.sh
+++ b/utils/check_code_format.sh
@@ -18,14 +18,16 @@
 #
 # This script assumes to be invoked at the project root directory.
 
-FILES_TO_CHECK=$(git diff --name-only master | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$")
+BASE_BRANCH=${1:-master}
+
+FILES_TO_CHECK=$(git diff --name-only ${BASE_BRANCH} | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$")
 
 if [ -z "${FILES_TO_CHECK}" ]; then
   echo "No source code to check for formatting."
   exit 0
 fi
 
-FORMAT_DIFF=$(git diff -U0 master -- ${FILES_TO_CHECK} | python ./utils/clang-format-diff.py -p1 -style=file)
+FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python ./utils/clang-format-diff.py -p1 -style=file)
 
 if [ -z "${FORMAT_DIFF}" ]; then
   echo "All source code in PR properly formatted."
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index fc249e9..5a95e32 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -16,7 +16,6 @@
 current directory.  Optionally insert them.  When inserting, replaces
 an MIT or Khronos free use license with Apache 2.
 """
-from __future__ import print_function
 
 import argparse
 import fileinput
diff --git a/utils/check_symbol_exports.py b/utils/check_symbol_exports.py
index c9c0364..e14c2eb 100755
--- a/utils/check_symbol_exports.py
+++ b/utils/check_symbol_exports.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Checks names of global exports from a library."""
 
-from __future__ import print_function
-
 import os.path
 import re
 import subprocess
@@ -56,6 +54,13 @@
     #   _ZN               :  something in a namespace
     #   _Z[0-9]+spv[A-Z_] :  C++ symbol starting with spv[A-Z_]
     symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_Z[0-9]+spv[A-Z_])')
+
+    # In addition, the following pattern whitelists global functions that are added
+    # by the protobuf compiler:
+    #   - AddDescriptors_spvtoolsfuzz_2eproto()
+    #   - InitDefaults_spvtoolsfuzz_2eproto()
+    symbol_whitelist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov')
+
     seen = set()
     result = 0
     for line in command_output(['objdump', '-t', library], '.').split('\n'):
@@ -65,7 +70,7 @@
             if symbol not in seen:
                 seen.add(symbol)
                 #print("look at '{}'".format(symbol))
-                if not symbol_ok_pattern.match(symbol):
+                if not (symbol_whitelist_pattern.match(symbol) or symbol_ok_pattern.match(symbol)):
                     print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
                     result = 1
     return result
@@ -81,7 +86,7 @@
         print('{}: error: {} does not exist'.format(PROG, args.library))
         sys.exit(1)
 
-    if os.name is 'posix':
+    if os.name == 'posix':
         status = check_library(args.library)
         sys.exit(status)
     else:
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index aabdad5..13f392a 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Generates various info tables from SPIR-V JSON grammar."""
 
-from __future__ import print_function
-
 import errno
 import json
 import os.path
@@ -36,8 +34,8 @@
 
 
 def make_path_to_file(f):
-    """Makes all ancestor directories to the given file, if they
-    don't yet exist.
+    """Makes all ancestor directories to the given file, if they don't yet
+    exist.
 
     Arguments:
         f: The file whose ancestor directories are to be created.
@@ -53,8 +51,8 @@
 
 
 def convert_min_required_version(version):
-    """Converts the minimal required SPIR-V version encoded in the
-    grammar to the symbol in SPIRV-Tools"""
+    """Converts the minimal required SPIR-V version encoded in the grammar to
+    the symbol in SPIRV-Tools."""
     if version is None:
         return 'SPV_SPIRV_VERSION_WORD(1, 0)'
     if version == 'None':
@@ -62,6 +60,14 @@
     return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ','))
 
 
+def convert_max_required_version(version):
+    """Converts the maximum required SPIR-V version encoded in the grammar to
+    the symbol in SPIRV-Tools."""
+    if version is None:
+        return '0xffffffffu'
+    return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ','))
+
+
 def compose_capability_list(caps):
     """Returns a string containing a braced list of capabilities as enums.
 
@@ -71,7 +77,7 @@
     Returns:
       a string containing the braced list of SpvCapability* enums named by caps.
     """
-    return "{" + ", ".join(['SpvCapability{}'.format(c) for c in caps]) + "}"
+    return '{' + ', '.join(['SpvCapability{}'.format(c) for c in caps]) + '}'
 
 
 def get_capability_array_name(caps):
@@ -108,8 +114,8 @@
     Returns:
       a string containing the braced list of extensions named by exts.
     """
-    return "{" + ", ".join(
-        ['spvtools::Extension::k{}'.format(e) for e in exts]) + "}"
+    return '{' + ', '.join(
+        ['spvtools::Extension::k{}'.format(e) for e in exts]) + '}'
 
 
 def get_extension_array_name(extensions):
@@ -140,8 +146,8 @@
 
 
 def convert_operand_kind(operand_tuple):
-    """Returns the corresponding operand type used in spirv-tools for
-    the given operand kind and quantifier used in the JSON grammar.
+    """Returns the corresponding operand type used in spirv-tools for the given
+    operand kind and quantifier used in the JSON grammar.
 
     Arguments:
       - operand_tuple: a tuple of two elements:
@@ -203,10 +209,10 @@
 
 
 class InstInitializer(object):
-    """Instances holds a SPIR-V instruction suitable for printing as
-    the initializer for spv_opcode_desc_t."""
+    """Instances holds a SPIR-V instruction suitable for printing as the
+    initializer for spv_opcode_desc_t."""
 
-    def __init__(self, opname, caps, exts, operands, version):
+    def __init__(self, opname, caps, exts, operands, version, lastVersion):
         """Initialization.
 
         Arguments:
@@ -215,6 +221,7 @@
           - exts: a sequence of names of extensions enabling this enumerant
           - operands: a sequence of (operand-kind, operand-quantifier) tuples
           - version: minimal SPIR-V version required for this opcode
+          - lastVersion: last version of SPIR-V that includes this opcode
         """
 
         assert opname.startswith('Op')
@@ -232,10 +239,11 @@
         self.def_result_id = 'IdResult' in operands
 
         self.version = convert_min_required_version(version)
+        self.lastVersion = convert_max_required_version(lastVersion)
 
     def fix_syntax(self):
-        """Fix an instruction's syntax, adjusting for differences between
-        the officially released grammar and how SPIRV-Tools uses the grammar.
+        """Fix an instruction's syntax, adjusting for differences between the
+        officially released grammar and how SPIRV-Tools uses the grammar.
 
         Fixes:
             - ExtInst should not end with SPV_OPERAND_VARIABLE_ID.
@@ -251,7 +259,7 @@
                     '{num_operands}', '{{{operands}}}',
                     '{def_result_id}', '{ref_type_id}',
                     '{num_exts}', '{exts}',
-                    '{min_version}}}']
+                    '{min_version}', '{max_version}}}']
         return ', '.join(template).format(
             opname=self.opname,
             num_caps=self.num_caps,
@@ -262,7 +270,8 @@
             ref_type_id=(1 if self.ref_type_id else 0),
             num_exts=self.num_exts,
             exts=self.exts,
-            min_version=self.version)
+            min_version=self.version,
+            max_version=self.lastVersion)
 
 
 class ExtInstInitializer(object):
@@ -315,18 +324,19 @@
     operands = inst.get('operands', {})
     operands = [(o['kind'], o.get('quantifier', '')) for o in operands]
     min_version = inst.get('version', None)
+    max_version = inst.get('lastVersion', None)
 
     assert opname is not None
 
     if is_ext_inst:
         return str(ExtInstInitializer(opname, opcode, caps, operands))
     else:
-        return str(InstInitializer(opname, caps, exts, operands, min_version))
+        return str(InstInitializer(opname, caps, exts, operands, min_version, max_version))
 
 
 def generate_instruction_table(inst_table):
-    """Returns the info table containing all SPIR-V instructions,
-    sorted by opcode, and prefixed by capability arrays.
+    """Returns the info table containing all SPIR-V instructions, sorted by
+    opcode, and prefixed by capability arrays.
 
     Note:
       - the built-in sorted() function is guaranteed to be stable.
@@ -370,7 +380,7 @@
 class EnumerantInitializer(object):
     """Prints an enumerant as the initializer for spv_operand_desc_t."""
 
-    def __init__(self, enumerant, value, caps, exts, parameters, version):
+    def __init__(self, enumerant, value, caps, exts, parameters, version, lastVersion):
         """Initialization.
 
         Arguments:
@@ -380,6 +390,7 @@
           - exts: a sequence of names of extensions enabling this enumerant
           - parameters: a sequence of (operand-kind, operand-quantifier) tuples
           - version: minimal SPIR-V version required for this opcode
+          - lastVersion: last SPIR-V version this opode appears
         """
         self.enumerant = enumerant
         self.value = value
@@ -389,11 +400,13 @@
         self.exts = get_extension_array_name(exts)
         self.parameters = [convert_operand_kind(p) for p in parameters]
         self.version = convert_min_required_version(version)
+        self.lastVersion = convert_max_required_version(lastVersion)
 
     def __str__(self):
         template = ['{{"{enumerant}"', '{value}', '{num_caps}',
                     '{caps}', '{num_exts}', '{exts}',
-                    '{{{parameters}}}', '{min_version}}}']
+                    '{{{parameters}}}', '{min_version}',
+                    '{max_version}}}']
         return ', '.join(template).format(
             enumerant=self.enumerant,
             value=self.value,
@@ -402,14 +415,16 @@
             num_exts=self.num_exts,
             exts=self.exts,
             parameters=', '.join(self.parameters),
-            min_version=self.version)
+            min_version=self.version,
+            max_version=self.lastVersion)
 
 
-def generate_enum_operand_kind_entry(entry):
+def generate_enum_operand_kind_entry(entry, extension_map):
     """Returns the C initializer for the given operand enum entry.
 
     Arguments:
       - entry: a dict containing information about an enum entry
+      - extension_map: a dict mapping enum value to list of extensions
 
     Returns:
       a string containing the C initializer for spv_operand_desc_t
@@ -417,35 +432,60 @@
     enumerant = entry.get('enumerant')
     value = entry.get('value')
     caps = entry.get('capabilities', [])
-    exts = entry.get('extensions', [])
+    if value in extension_map:
+        exts = extension_map[value]
+    else:
+        exts = []
     params = entry.get('parameters', [])
     params = [p.get('kind') for p in params]
     params = zip(params, [''] * len(params))
     version = entry.get('version', None)
+    max_version = entry.get('lastVersion', None)
 
     assert enumerant is not None
     assert value is not None
 
     return str(EnumerantInitializer(
-        enumerant, value, caps, exts, params, version))
+        enumerant, value, caps, exts, params, version, max_version))
 
 
-def generate_enum_operand_kind(enum):
-    """Returns the C definition for the given operand kind."""
+def generate_enum_operand_kind(enum, synthetic_exts_list):
+    """Returns the C definition for the given operand kind.
+
+    Also appends to |synthetic_exts_list| a list of extension lists
+    used.
+    """
     kind = enum.get('kind')
     assert kind is not None
 
-    # Sort all enumerants first according to their values and then
-    # their names so that the symbols with the same values are
-    # grouped together.
+    # Sort all enumerants according to their values, but otherwise
+    # preserve their order so the first name listed in the grammar
+    # as the preferred name for disassembly.
     if enum.get('category') == 'ValueEnum':
-        functor = lambda k: (k['value'], k['enumerant'])
+        def functor(k): return (k['value'])
     else:
-        functor = lambda k: (int(k['value'], 16), k['enumerant'])
+        def functor(k): return (int(k['value'], 16))
     entries = sorted(enum.get('enumerants', []), key=functor)
 
+    # SubgroupEqMask and SubgroupEqMaskKHR are the same number with
+    # same semantics, but one has no extension list while the other
+    # does.  Both should have the extension list.
+    # So create a mapping from enum value to the union of the extensions
+    # across all those grammar entries.  Preserve order.
+    extension_map = {}
+    for e in entries:
+        value = e.get('value')
+        extension_map[value] = []
+    for e in entries:
+        value = e.get('value')
+        exts = e.get('extensions', [])
+        for ext in exts:
+            if ext not in extension_map[value]:
+                extension_map[value].append(ext)
+    synthetic_exts_list.extend(extension_map.values())
+
     name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind)
-    entries = ['  {}'.format(generate_enum_operand_kind_entry(e))
+    entries = ['  {}'.format(generate_enum_operand_kind_entry(e, extension_map))
                for e in entries]
 
     template = ['static const spv_operand_desc_t {name}[] = {{',
@@ -470,9 +510,9 @@
     exts = [entry.get('extensions', [])
             for enum in enums
             for entry in enum.get('enumerants', [])]
+    enums = [generate_enum_operand_kind(e, exts) for e in enums]
     exts_arrays = generate_extension_arrays(exts)
 
-    enums = [generate_enum_operand_kind(e) for e in enums]
     # We have three operand kinds that requires their optional counterpart to
     # exist in the operand info table.
     three_optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess']
@@ -504,7 +544,8 @@
 
     things_with_an_extensions_field = [item for item in instructions]
 
-    enumerants = sum([item.get('enumerants', []) for item in operand_kinds], [])
+    enumerants = sum([item.get('enumerants', [])
+                      for item in operand_kinds], [])
 
     things_with_an_extensions_field.extend(enumerants)
 
@@ -513,11 +554,12 @@
                       if item.get('extensions')], [])
 
     for item in EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split():
-        # If it's already listed in a grammar, then don't put it in the
-        # special exceptions list.
-        assert item not in extensions, "Extension %s is already in a grammar file" % item
+            # If it's already listed in a grammar, then don't put it in the
+            # special exceptions list.
+        assert item not in extensions, 'Extension %s is already in a grammar file' % item
 
-    extensions.extend(EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split())
+    extensions.extend(
+        EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split())
 
     # Validator would ignore type declaration unique check. Should only be used
     # for legacy autogenerated test files containing multiple instances of the
@@ -530,8 +572,7 @@
 
 def get_capabilities(operand_kinds):
     """Returns capabilities as a list of JSON objects, in order of
-    appearance.
-    """
+    appearance."""
     enumerants = sum([item.get('enumerants', []) for item in operand_kinds
                       if item.get('kind') in ['Capability']], [])
     return enumerants
@@ -580,6 +621,7 @@
 
 def generate_capability_to_string_mapping(operand_kinds):
     """Returns mapping function from capabilities to corresponding strings.
+
     We take care to avoid emitting duplicate values.
     """
     function = 'const char* CapabilityToString(SpvCapability capability) {\n'
@@ -608,6 +650,12 @@
     return '\n\n'.join(tables)
 
 
+def precondition_operand_kinds(operand_kinds):
+    """For operand kinds that have the same number, make sure they all have the
+    same extension list."""
+    return operand_kinds
+
+
 def main():
     import argparse
     parser = argparse.ArgumentParser(description='Generate SPIR-V info tables')
@@ -701,48 +749,51 @@
                 operand_kinds.extend(core_grammar['operand_kinds'])
                 operand_kinds.extend(debuginfo_grammar['operand_kinds'])
                 extensions = get_extension_list(instructions, operand_kinds)
+                operand_kinds = precondition_operand_kinds(operand_kinds)
         if args.core_insts_output is not None:
             make_path_to_file(args.core_insts_output)
             make_path_to_file(args.operand_kinds_output)
-            print(generate_instruction_table(core_grammar['instructions']),
-              file=open(args.core_insts_output, 'w'))
-            print(generate_operand_kind_table(operand_kinds),
-              file=open(args.operand_kinds_output, 'w'))
+            with open(args.core_insts_output, 'w') as f:
+                f.write(generate_instruction_table(
+                    core_grammar['instructions']))
+            with open(args.operand_kinds_output, 'w') as f:
+                f.write(generate_operand_kind_table(operand_kinds))
         if args.extension_enum_output is not None:
             make_path_to_file(args.extension_enum_output)
-            print(generate_extension_enum(extensions),
-              file=open(args.extension_enum_output, 'w'))
+            with open(args.extension_enum_output, 'w') as f:
+                f.write(generate_extension_enum(extensions))
         if args.enum_string_mapping_output is not None:
             make_path_to_file(args.enum_string_mapping_output)
-            print(generate_all_string_enum_mappings(extensions, operand_kinds),
-              file=open(args.enum_string_mapping_output, 'w'))
+            with open(args.enum_string_mapping_output, 'w') as f:
+                f.write(generate_all_string_enum_mappings(
+                    extensions, operand_kinds))
 
     if args.extinst_glsl_grammar is not None:
         with open(args.extinst_glsl_grammar) as json_file:
             grammar = json.loads(json_file.read())
             make_path_to_file(args.glsl_insts_output)
-            print(generate_extended_instruction_table(
-                    grammar['instructions'], "glsl"),
-                  file=open(args.glsl_insts_output, 'w'))
+            with open(args.glsl_insts_output, 'w') as f:
+                f.write(generate_extended_instruction_table(
+                    grammar['instructions'], 'glsl'))
 
     if args.extinst_opencl_grammar is not None:
         with open(args.extinst_opencl_grammar) as json_file:
             grammar = json.loads(json_file.read())
             make_path_to_file(args.opencl_insts_output)
-            print(generate_extended_instruction_table(
-                    grammar['instructions'], "opencl"),
-                  file=open(args.opencl_insts_output, 'w'))
+            with open(args.opencl_insts_output, 'w') as f:
+                f.write(generate_extended_instruction_table(
+                    grammar['instructions'], 'opencl'))
 
     if args.extinst_vendor_grammar is not None:
         with open(args.extinst_vendor_grammar) as json_file:
             grammar = json.loads(json_file.read())
             make_path_to_file(args.vendor_insts_output)
             name = args.extinst_vendor_grammar
-            start = name.find("extinst.") + len("extinst.")
-            name = name[start:-len(".grammar.json")].replace("-", "_")
-            print(generate_extended_instruction_table(
-                    grammar['instructions'], name),
-                  file=open(args.vendor_insts_output, 'w'))
+            start = name.find('extinst.') + len('extinst.')
+            name = name[start:-len('.grammar.json')].replace('-', '_')
+            with open(args.vendor_insts_output, 'w') as f:
+                f.write(generate_extended_instruction_table(
+                    grammar['instructions'], name))
 
 
 if __name__ == '__main__':
diff --git a/utils/generate_language_headers.py b/utils/generate_language_headers.py
index 1886bf4..0296163 100755
--- a/utils/generate_language_headers.py
+++ b/utils/generate_language_headers.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Generates language headers from a JSON grammar file"""
 
-from __future__ import print_function
-
 import errno
 import json
 import os.path
@@ -181,7 +179,8 @@
                                  version = grammar_json['version'],
                                  revision = grammar_json['revision'])
         make_path_to_file(args.extinst_output_base)
-        print(CGenerator().generate(grammar), file=open(args.extinst_output_base + '.h', 'w'))
+        with open(args.extinst_output_base + '.h', 'w') as f:
+            f.write(CGenerator().generate(grammar))
 
 
 if __name__ == '__main__':
diff --git a/utils/generate_registry_tables.py b/utils/generate_registry_tables.py
index 8b1c357..e662ba9 100755
--- a/utils/generate_registry_tables.py
+++ b/utils/generate_registry_tables.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Generates the vendor tool table from the SPIR-V XML registry."""
 
-from __future__ import print_function
-
 import distutils.dir_util
 import os.path
 import xml.etree.ElementTree
@@ -65,7 +63,8 @@
        registry = xml.etree.ElementTree.fromstring(xml_in.read())
 
     distutils.dir_util.mkpath(os.path.dirname(args.generator_output))
-    print(generate_vendor_table(registry), file=open(args.generator_output, 'w'))
+    with open(args.generator_output, 'w') as f:
+      f.write(generate_vendor_table(registry))
 
 
 if __name__ == '__main__':
diff --git a/utils/generate_vim_syntax.py b/utils/generate_vim_syntax.py
index 03c0b47..da7e99b 100755
--- a/utils/generate_vim_syntax.py
+++ b/utils/generate_vim_syntax.py
@@ -14,8 +14,6 @@
 # limitations under the License.
 """Generates Vim syntax rules for SPIR-V assembly (.spvasm) files"""
 
-from __future__ import print_function
-
 import json
 
 PREAMBLE="""" Vim syntax file
diff --git a/utils/git-sync-deps b/utils/git-sync-deps
new file mode 100755
index 0000000..0575641
--- /dev/null
+++ b/utils/git-sync-deps
@@ -0,0 +1,282 @@
+#!/usr/bin/env python
+# Copyright 2014 Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#    * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#    * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#    * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Parse a DEPS file and git checkout all of the dependencies.
+
+Args:
+  An optional list of deps_os values.
+
+Environment Variables:
+  GIT_EXECUTABLE: path to "git" binary; if unset, will look for one of
+  ['git', 'git.exe', 'git.bat'] in your default path.
+
+  GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset,
+  will use the file ../DEPS relative to this script's directory.
+
+  GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages.
+
+Git Config:
+  To disable syncing of a single repository:
+      cd path/to/repository
+      git config sync-deps.disable true
+
+  To re-enable sync:
+      cd path/to/repository
+      git config --unset sync-deps.disable
+"""
+
+
+import os
+import re
+import subprocess
+import sys
+import threading
+from builtins import bytes
+
+
+def git_executable():
+  """Find the git executable.
+
+  Returns:
+      A string suitable for passing to subprocess functions, or None.
+  """
+  envgit = os.environ.get('GIT_EXECUTABLE')
+  searchlist = ['git', 'git.exe', 'git.bat']
+  if envgit:
+    searchlist.insert(0, envgit)
+  with open(os.devnull, 'w') as devnull:
+    for git in searchlist:
+      try:
+        subprocess.call([git, '--version'], stdout=devnull)
+      except (OSError,):
+        continue
+      return git
+  return None
+
+
+DEFAULT_DEPS_PATH = os.path.normpath(
+  os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
+
+
+def usage(deps_file_path = None):
+  sys.stderr.write(
+    'Usage: run to grab dependencies, with optional platform support:\n')
+  sys.stderr.write('  %s %s' % (sys.executable, __file__))
+  if deps_file_path:
+    parsed_deps = parse_file_to_dict(deps_file_path)
+    if 'deps_os' in parsed_deps:
+      for deps_os in parsed_deps['deps_os']:
+        sys.stderr.write(' [%s]' % deps_os)
+  sys.stderr.write('\n\n')
+  sys.stderr.write(__doc__)
+
+
+def git_repository_sync_is_disabled(git, directory):
+  try:
+    disable = subprocess.check_output(
+      [git, 'config', 'sync-deps.disable'], cwd=directory)
+    return disable.lower().strip() in ['true', '1', 'yes', 'on']
+  except subprocess.CalledProcessError:
+    return False
+
+
+def is_git_toplevel(git, directory):
+  """Return true iff the directory is the top level of a Git repository.
+
+  Args:
+    git (string) the git executable
+
+    directory (string) the path into which the repository
+              is expected to be checked out.
+  """
+  try:
+    toplevel = subprocess.check_output(
+      [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip()
+    return os.path.realpath(bytes(directory, 'utf8')) == os.path.realpath(toplevel)
+  except subprocess.CalledProcessError:
+    return False
+
+
+def status(directory, checkoutable):
+  def truncate(s, length):
+    return s if len(s) <= length else s[:(length - 3)] + '...'
+  dlen = 36
+  directory = truncate(directory, dlen)
+  checkoutable = truncate(checkoutable, 40)
+  sys.stdout.write('%-*s @ %s\n' % (dlen, directory, checkoutable))
+
+
+def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
+  """Checkout (and clone if needed) a Git repository.
+
+  Args:
+    git (string) the git executable
+
+    repo (string) the location of the repository, suitable
+         for passing to `git clone`.
+
+    checkoutable (string) a tag, branch, or commit, suitable for
+                 passing to `git checkout`
+
+    directory (string) the path into which the repository
+              should be checked out.
+
+    verbose (boolean)
+
+  Raises an exception if any calls to git fail.
+  """
+  if not os.path.isdir(directory):
+    subprocess.check_call(
+      [git, 'clone', '--quiet', repo, directory])
+
+  if not is_git_toplevel(git, directory):
+    # if the directory exists, but isn't a git repo, you will modify
+    # the parent repostory, which isn't what you want.
+    sys.stdout.write('%s\n  IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
+    return
+
+  # Check to see if this repo is disabled.  Quick return.
+  if git_repository_sync_is_disabled(git, directory):
+    sys.stdout.write('%s\n  SYNC IS DISABLED.\n' % directory)
+    return
+
+  with open(os.devnull, 'w') as devnull:
+    # If this fails, we will fetch before trying again.  Don't spam user
+    # with error infomation.
+    if 0 == subprocess.call([git, 'checkout', '--quiet', checkoutable],
+                            cwd=directory, stderr=devnull):
+      # if this succeeds, skip slow `git fetch`.
+      if verbose:
+        status(directory, checkoutable)  # Success.
+      return
+
+  # If the repo has changed, always force use of the correct repo.
+  # If origin already points to repo, this is a quick no-op.
+  subprocess.check_call(
+      [git, 'remote', 'set-url', 'origin', repo], cwd=directory)
+
+  subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
+
+  subprocess.check_call([git, 'checkout', '--quiet', checkoutable], cwd=directory)
+
+  if verbose:
+    status(directory, checkoutable)  # Success.
+
+
+def parse_file_to_dict(path):
+  dictionary = {}
+  contents = open(path).read()
+  # Need to convert Var() to vars[], so that the DEPS is actually Python. Var()
+  # comes from Autoroller using gclient which has a slightly different DEPS
+  # format.
+  contents = re.sub(r"Var\((.*?)\)", r"vars[\1]", contents)
+  exec(contents, dictionary)
+  return dictionary
+
+
+def git_sync_deps(deps_file_path, command_line_os_requests, verbose):
+  """Grab dependencies, with optional platform support.
+
+  Args:
+    deps_file_path (string) Path to the DEPS file.
+
+    command_line_os_requests (list of strings) Can be empty list.
+        List of strings that should each be a key in the deps_os
+        dictionary in the DEPS file.
+
+  Raises git Exceptions.
+  """
+  git = git_executable()
+  assert git
+
+  deps_file_directory = os.path.dirname(deps_file_path)
+  deps_file = parse_file_to_dict(deps_file_path)
+  dependencies = deps_file['deps'].copy()
+  os_specific_dependencies = deps_file.get('deps_os', dict())
+  if 'all' in command_line_os_requests:
+    for value in list(os_specific_dependencies.values()):
+      dependencies.update(value)
+  else:
+    for os_name in command_line_os_requests:
+      # Add OS-specific dependencies
+      if os_name in os_specific_dependencies:
+        dependencies.update(os_specific_dependencies[os_name])
+  for directory in dependencies:
+    for other_dir in dependencies:
+      if directory.startswith(other_dir + '/'):
+        raise Exception('%r is parent of %r' % (other_dir, directory))
+  list_of_arg_lists = []
+  for directory in sorted(dependencies):
+    if '@' in dependencies[directory]:
+      repo, checkoutable = dependencies[directory].split('@', 1)
+    else:
+      raise Exception("please specify commit or tag")
+
+    relative_directory = os.path.join(deps_file_directory, directory)
+
+    list_of_arg_lists.append(
+      (git, repo, checkoutable, relative_directory, verbose))
+
+  multithread(git_checkout_to_directory, list_of_arg_lists)
+
+  for directory in deps_file.get('recursedeps', []):
+    recursive_path = os.path.join(deps_file_directory, directory, 'DEPS')
+    git_sync_deps(recursive_path, command_line_os_requests, verbose)
+
+
+def multithread(function, list_of_arg_lists):
+  # for args in list_of_arg_lists:
+  #   function(*args)
+  # return
+  threads = []
+  for args in list_of_arg_lists:
+    thread = threading.Thread(None, function, None, args)
+    thread.start()
+    threads.append(thread)
+  for thread in threads:
+    thread.join()
+
+
+def main(argv):
+  deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
+  verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
+
+  if '--help' in argv or '-h' in argv:
+    usage(deps_file_path)
+    return 1
+
+  git_sync_deps(deps_file_path, argv, verbose)
+  # subprocess.check_call(
+  #     [sys.executable,
+  #      os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')])
+  return 0
+
+
+if __name__ == '__main__':
+  exit(main(sys.argv[1:]))
diff --git a/utils/roll_deps.sh b/utils/roll_deps.sh
new file mode 100755
index 0000000..622afc9
--- /dev/null
+++ b/utils/roll_deps.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Copyright (c) 2019 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.
+
+# Attempts to roll all entries in DEPS to origin/master and creates a
+# commit.
+#
+# Depends on roll-dep from depot_path being in PATH.
+
+# This script assumes it's parent directory is the repo root.
+repo_path=$(dirname "$0")/..
+
+effcee_dir="external/effcee/"
+googletest_dir="external/googletest/"
+re2_dir="external/re2/"
+spirv_headers_dir="external/spirv-headers/"
+
+cd "$repo_path"
+
+roll-dep "$@" "${effcee_dir}" "${googletest_dir}" "${re2_dir}" "${spirv_headers_dir}"
diff --git a/utils/update_build_version.py b/utils/update_build_version.py
index d71aecc..119bb05 100755
--- a/utils/update_build_version.py
+++ b/utils/update_build_version.py
@@ -29,8 +29,6 @@
 #    "unknown hash".
 # The string contents are escaped as necessary.
 
-from __future__ import print_function
-
 import datetime
 import errno
 import os